From 687159a720bd16e9f35afc8fcf4af4ed9e16dbe4 Mon Sep 17 00:00:00 2001
From: SoggyRihno <94922205+SoggyRihno@users.noreply.github.com>
Date: Thu, 4 Dec 2025 18:39:10 -0600
Subject: [PATCH 1/5] cleanup loader poc test
---
parser/gradeLoader.go | 96 +++++++++++++-------------------------
parser/gradeLoader_test.go | 14 ++++++
parser/parser.go | 22 ++++++---
3 files changed, 61 insertions(+), 71 deletions(-)
create mode 100644 parser/gradeLoader_test.go
diff --git a/parser/gradeLoader.go b/parser/gradeLoader.go
index 57c7d7a..7ae54bc 100644
--- a/parser/gradeLoader.go
+++ b/parser/gradeLoader.go
@@ -3,81 +3,48 @@ package parser
import (
"encoding/csv"
"fmt"
+ "github.com/UTDNebula/api-tools/utils"
"log"
"os"
- "path/filepath"
"strconv"
"strings"
)
-var grades = []string{"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-", "F", "W", "P", "CR", "NC", "I"}
-
-func loadGrades(csvDir string) map[string]map[string][]int {
+var (
+ grades = []string{"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-", "F", "W", "P", "CR", "NC", "I"}
+ optionalColumns = []string{"W", "P", "CR", "NC", "I"}
+ requiredColumns = []string{"Section", "Subject", "Catalog Number", "A+"}
+)
+func loadGrades(csvDir string) (map[string]map[string][]int, error) {
// MAP[SEMESTER] -> MAP[SUBJECT + NUMBER + SECTION] -> GRADE DISTRIBUTION
gradeMap := make(map[string]map[string][]int)
- if csvDir == "" {
- log.Print("No grade data CSV directory specified. Grade data will not be included.")
- return gradeMap
- }
-
- dirPtr, err := os.Open(csvDir)
- if err != nil {
- panic(err)
- }
- defer dirPtr.Close()
+ fileNames := utils.GetAllFilesWithExtension(csvDir, ".csv")
+ for _, name := range fileNames {
- csvFiles, err := dirPtr.ReadDir(-1)
- if err != nil {
- panic(err)
- }
-
- for _, csvEntry := range csvFiles {
-
- if csvEntry.IsDir() {
- continue
- }
-
- csvPath := fmt.Sprintf("%s/%s", csvDir, csvEntry.Name())
-
- csvFile, err := os.Open(csvPath)
+ var err error
+ gradeMap[name], err = csvToMap(name)
if err != nil {
- panic(err)
- }
- defer csvFile.Close()
-
- // Create logs directory
- if _, err := os.Stat("./logs/grades"); err != nil {
- os.Mkdir("./logs/grades", os.ModePerm)
+ return gradeMap, fmt.Errorf("error parsing %s: %v", name, err)
}
-
- // Create log file [name of csv].log in logs directory
- basePath := filepath.Base(csvPath)
- csvName := strings.TrimSuffix(basePath, filepath.Ext(basePath))
- logFile, err := os.Create("./logs/grades/" + csvName + ".log")
-
- if err != nil {
- log.Panic("Could not create CSV log file.")
- }
- defer logFile.Close()
-
- // Put data from csv into map
- gradeMap[csvName] = csvToMap(csvFile, logFile)
}
-
- return gradeMap
+ return gradeMap, nil
}
-func csvToMap(csvFile *os.File, logFile *os.File) map[string][]int {
- reader := csv.NewReader(csvFile)
- records, err := reader.ReadAll() // records is [][]strings
+func csvToMap(filename string) (map[string][]int, error) {
+ file, err := os.Open(filename)
if err != nil {
- log.Panicf("Error parsing %s: %s", csvFile.Name(), err.Error())
+ return nil, fmt.Errorf("error opening CSV file '%s': %v", filename, err)
}
- indexMap := make(map[string]int)
+ reader := csv.NewReader(file)
+ records, err := reader.ReadAll()
+ if err != nil {
+ return nil, fmt.Errorf("error parsing %s: %v", filename, err)
+ }
+ indexMap := make(map[string]int)
for j, col := range records[0] {
switch col {
case "Catalog Number", "Catalog Nbr":
@@ -89,18 +56,15 @@ func csvToMap(csvFile *os.File, logFile *os.File) map[string][]int {
}
}
- // required columns
- for _, name := range []string{"Section", "Subject", "Catalog Number", "A+"} {
+ for _, name := range requiredColumns {
if _, ok := indexMap[name]; !ok {
- fmt.Fprintf(logFile, "could not find %s column", name)
- log.Panicf("could not find %s column", name)
+ return nil, fmt.Errorf("could not find %s column in %s", name, filename)
}
}
- // optional columns
- for _, name := range []string{"W", "P", "CR", "NC", "I"} {
+ for _, name := range optionalColumns {
if _, ok := indexMap[name]; !ok {
- logFile.WriteString(fmt.Sprintf("could not find %s column\n", name))
+ log.Printf("could not find %s column in %s", name, filename)
}
}
@@ -109,7 +73,6 @@ func csvToMap(csvFile *os.File, logFile *os.File) map[string][]int {
catalogNumberCol := indexMap["Catalog Number"]
distroMap := make(map[string][]int)
-
for _, record := range records[1:] {
// convert grade distribution from string to int
intSlice := make([]int, len(grades))
@@ -125,5 +88,10 @@ func csvToMap(csvFile *os.File, logFile *os.File) map[string][]int {
distroKey := record[subjectCol] + record[catalogNumberCol] + trimmedSectionNumber
distroMap[distroKey] = intSlice[:]
}
- return distroMap
+
+ if err := file.Close(); err != nil {
+ return nil, fmt.Errorf("failed to close file '%s': %v", filename, err)
+ }
+
+ return distroMap, nil
}
diff --git a/parser/gradeLoader_test.go b/parser/gradeLoader_test.go
new file mode 100644
index 0000000..b7a414b
--- /dev/null
+++ b/parser/gradeLoader_test.go
@@ -0,0 +1,14 @@
+package parser
+
+import (
+ "testing"
+)
+
+func TestLoadGrades(t *testing.T) {
+
+ _, err := loadGrades("../grade-data/")
+ if err != nil {
+ t.Errorf("loadGrades() error = %v", err)
+ }
+
+}
diff --git a/parser/parser.go b/parser/parser.go
index 2b7049e..1dd2241 100644
--- a/parser/parser.go
+++ b/parser/parser.go
@@ -40,17 +40,25 @@ var (
timeLocation, timeError = time.LoadLocation("America/Chicago")
)
+func init() {
+ if timeError != nil {
+ log.Fatalf("Failed to initialize timeLocation: %v", timeError)
+ }
+}
+
// Parse loads scraped course artifacts, applies parsing and validation, and persists structured results.
func Parse(inDir string, outDir string, csvPath string, skipValidation bool) {
- // Panic if timeLocation didn't load properly
- if timeError != nil {
- panic(timeError)
- }
+ if csvPath == "" {
+ log.Print("No grade data CSV directory specified. Grade data will not be included.")
+ } else {
+ var err error
+ GradeMap, err = loadGrades(csvPath)
- // Load grade data from csv in advance
- GradeMap = loadGrades(csvPath)
- if len(GradeMap) != 0 {
+ if err != nil {
+ log.Fatalf("Failed to load grade data: %v", err)
+ return
+ }
log.Printf("Loaded grade distributions for %d semesters.", len(GradeMap))
}
From 7513640730ac343ce4d67828045e3c47c0f17b74 Mon Sep 17 00:00:00 2001
From: SoggyRihno <94922205+SoggyRihno@users.noreply.github.com>
Date: Thu, 22 Jan 2026 20:53:34 -0600
Subject: [PATCH 2/5] Cleaned up gradeLoader code and updated parser tests to
use grade data
---
parser/gradeLoader.go | 22 +++++++++----
parser/parser_test.go | 77 +++++++++++++++++++++++++++++++++++--------
utils/methods.go | 4 +--
3 files changed, 81 insertions(+), 22 deletions(-)
diff --git a/parser/gradeLoader.go b/parser/gradeLoader.go
index 7ae54bc..43e4855 100644
--- a/parser/gradeLoader.go
+++ b/parser/gradeLoader.go
@@ -3,17 +3,20 @@ package parser
import (
"encoding/csv"
"fmt"
- "github.com/UTDNebula/api-tools/utils"
"log"
"os"
+ "regexp"
"strconv"
"strings"
+
+ "github.com/UTDNebula/api-tools/utils"
)
var (
grades = []string{"A+", "A", "A-", "B+", "B", "B-", "C+", "C", "C-", "D+", "D", "D-", "F", "W", "P", "CR", "NC", "I"}
optionalColumns = []string{"W", "P", "CR", "NC", "I"}
requiredColumns = []string{"Section", "Subject", "Catalog Number", "A+"}
+ semesterRegex = regexp.MustCompile(`[1-9][0-9][USF]`)
)
func loadGrades(csvDir string) (map[string]map[string][]int, error) {
@@ -23,12 +26,18 @@ func loadGrades(csvDir string) (map[string]map[string][]int, error) {
fileNames := utils.GetAllFilesWithExtension(csvDir, ".csv")
for _, name := range fileNames {
+ semester := semesterRegex.FindString(name)
+ if semester == "" {
+ return gradeMap, fmt.Errorf("invalid name %s, must match format {>10}{F,S,U} i.e. 22F", name)
+ }
+
var err error
- gradeMap[name], err = csvToMap(name)
+ gradeMap[semester], err = csvToMap(name)
if err != nil {
return gradeMap, fmt.Errorf("error parsing %s: %v", name, err)
}
}
+
return gradeMap, nil
}
@@ -37,6 +46,11 @@ func csvToMap(filename string) (map[string][]int, error) {
if err != nil {
return nil, fmt.Errorf("error opening CSV file '%s': %v", filename, err)
}
+ defer func(file *os.File) {
+ if err := file.Close(); err != nil {
+ log.Printf("failed to close file '%s': %v", filename, err)
+ }
+ }(file)
reader := csv.NewReader(file)
records, err := reader.ReadAll()
@@ -89,9 +103,5 @@ func csvToMap(filename string) (map[string][]int, error) {
distroMap[distroKey] = intSlice[:]
}
- if err := file.Close(); err != nil {
- return nil, fmt.Errorf("failed to close file '%s': %v", filename, err)
- }
-
return distroMap, nil
}
diff --git a/parser/parser_test.go b/parser/parser_test.go
index cee8873..41e7af4 100644
--- a/parser/parser_test.go
+++ b/parser/parser_test.go
@@ -34,7 +34,7 @@ type TestData struct {
// testData global dictionary containing the data from /testdata by folder name
var testData map[string]TestData
-// TestMain loads parser fixtures and handles the -update flag for regenerating expectations.
+// TestMain loads parser fixtures and handles the `-update` flag for regenerating expectations.
func TestMain(m *testing.M) {
update := flag.Bool("update", false, "Regenerates the expected output for the provided test inputs. Should only be used when you are 100% sure your code is correct! It will make all test pass :)")
@@ -132,10 +132,13 @@ func updateTestData() error {
}
defer os.RemoveAll(tempDir)
- //Fill temp dir with all the test cases and expected values
+ GradeMap, err = loadGrades("../grade-data")
+ if err != nil {
+ return err
+ }
+ //Fill temp dir with all the test cases and expected values
duplicates := make(map[string]bool)
-
for i, input := range utils.GetAllFilesWithExtension("testdata", ".html") {
parse(input)
@@ -214,20 +217,66 @@ func updateTestData() error {
//rerun parser to get Courses.json, Sections.json, Professors.json
- //Parse(tempDir, tempDir, "../grade-data", false)
- //Grade data isn't work with tests currently
- Parse(tempDir, tempDir, "", false)
+ Parse(tempDir, tempDir, "../grade-data", false)
+
+ targetDir := "testdata"
+
+ err = filepath.Walk(tempDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ relPath, err := filepath.Rel(tempDir, path)
+ if err != nil {
+ return err
+ }
+ destPath := filepath.Join(targetDir, relPath)
+
+ if info.IsDir() {
+ return os.MkdirAll(destPath, 0755)
+ }
+
+ newContent, err := os.ReadFile(path)
+ if err != nil {
+ return err
+ }
+
+ if existingContent, err := os.ReadFile(destPath); err == nil {
+ if bytes.Equal(newContent, existingContent) {
+ return nil
+ }
+ }
+
+ log.Printf("Updating file: %s", destPath)
+ return os.WriteFile(destPath, newContent, 0644)
+ })
- //overwrite the current test data with the new data
- if err := os.RemoveAll("testdata"); err != nil {
- return fmt.Errorf("failed to remove testdata: %v", err)
+ if err != nil {
+ return fmt.Errorf("failed to sync test data: %v", err)
}
- if err := os.CopyFS("testdata", os.DirFS(tempDir)); err != nil {
- return fmt.Errorf("failed to copy testdata: %v", err)
+ err = filepath.Walk(targetDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+
+ relPath, err := filepath.Rel(targetDir, path)
+ if err != nil {
+ return err
+ }
+
+ srcPath := filepath.Join(tempDir, relPath)
+ if _, err := os.Stat(srcPath); os.IsNotExist(err) {
+ log.Printf("Removing stale file: %s", path)
+ return os.RemoveAll(path)
+ }
+ return nil
+ })
+
+ if err != nil {
+ return fmt.Errorf("failed to cleanup stale data: %v", err)
}
- //reset maps to avoid side effects. maybe parser should be an object?
clearGlobals()
return nil
}
@@ -244,8 +293,8 @@ func clearGlobals() {
// TestParse verifies that parsing input fixtures generates the expected JSON exports.
func TestParse(t *testing.T) {
tempDir := t.TempDir()
- // todo fix grade data, csvPath = ./grade-data panics
- Parse("testdata", tempDir, "", false)
+
+ Parse("testdata", tempDir, "../grade-data", false)
OutputCourses, err := unmarshallFile[[]schema.Course](filepath.Join(tempDir, "courses.json"))
if err != nil {
diff --git a/utils/methods.go b/utils/methods.go
index 90712e2..70b8bfe 100644
--- a/utils/methods.go
+++ b/utils/methods.go
@@ -224,8 +224,8 @@ func WriteJSON(filepath string, data interface{}) error {
defer fptr.Close()
encoder := json.NewEncoder(fptr)
encoder.SetIndent("", "\t")
- encoder.Encode(data)
- return nil
+ encoder.SetEscapeHTML(false)
+ return encoder.Encode(data)
}
// GetAllFilesWithExtension recursively gathers file paths within inDir that match extension.
From f5cf757aa71f853099f1c2ad83225b7a6af484dc Mon Sep 17 00:00:00 2001
From: SoggyRihno <94922205+SoggyRihno@users.noreply.github.com>
Date: Thu, 22 Jan 2026 20:53:59 -0600
Subject: [PATCH 3/5] New testcase
---
parser/testdata/case_006/classInfo.json | 14 ++
parser/testdata/case_006/course.json | 25 +++
parser/testdata/case_006/input.html | 268 +++++++++++++++++++++++
parser/testdata/case_006/professors.json | 23 ++
parser/testdata/case_006/section.json | 61 ++++++
5 files changed, 391 insertions(+)
create mode 100644 parser/testdata/case_006/classInfo.json
create mode 100644 parser/testdata/case_006/course.json
create mode 100644 parser/testdata/case_006/input.html
create mode 100644 parser/testdata/case_006/professors.json
create mode 100644 parser/testdata/case_006/section.json
diff --git a/parser/testdata/case_006/classInfo.json b/parser/testdata/case_006/classInfo.json
new file mode 100644
index 0000000..ae81d1c
--- /dev/null
+++ b/parser/testdata/case_006/classInfo.json
@@ -0,0 +1,14 @@
+{
+ "": "",
+ "Activity Type:": "Lecture",
+ "Add Consent:": "No Consent",
+ "Class Level:": "Undergraduate",
+ "Class Section:": "THEA1310.001.25S",
+ "Class/Course Number:": "24043 / 003909",
+ "Grading:": "Graded - Undergraduate",
+ "How often a course is scheduled:": "Once Each Long Semester",
+ "Instruction Mode:": "Face-to-Face",
+ "Orion Date/Time:": "2025-08-28 06:30:01",
+ "Semester Credit Hours:": "3",
+ "Session Type:": "Regular Academic Session"
+}
diff --git a/parser/testdata/case_006/course.json b/parser/testdata/case_006/course.json
new file mode 100644
index 0000000..26de202
--- /dev/null
+++ b/parser/testdata/case_006/course.json
@@ -0,0 +1,25 @@
+{
+ "_id": "6972d72a922d8b0bc3a3bce0",
+ "subject_prefix": "THEA",
+ "course_number": "1310",
+ "title": "Understanding Theatre",
+ "description": "THEA 1310 - Understanding Theatre (3 semester credit hours) Lectures, discussions, and performances designed to explore artistic, philosophical, social, historical, and psychological dimensions of the theatrical experience. Topics may include analysis of scripts, the nature of the theater compared to the other performing arts, and the nature of popular entertainments. (3-0) S",
+ "enrollment_reqs": "",
+ "school": "School of Arts, Humanities, and Technology",
+ "credit_hours": "3",
+ "class_level": "Undergraduate",
+ "activity_type": "Lecture",
+ "grading": "Graded - Undergraduate",
+ "internal_course_number": "003909",
+ "prerequisites": null,
+ "corequisites": null,
+ "co_or_pre_requisites": null,
+ "sections": [
+ "6972d72a922d8b0bc3a3bce1"
+ ],
+ "lecture_contact_hours": "3",
+ "laboratory_contact_hours": "0",
+ "offering_frequency": "S",
+ "catalog_year": "24",
+ "attributes": null
+}
diff --git a/parser/testdata/case_006/input.html b/parser/testdata/case_006/input.html
new file mode 100644
index 0000000..e4efe97
--- /dev/null
+++ b/parser/testdata/case_006/input.html
@@ -0,0 +1,268 @@
+
+
+
+
+ class overview
+
+
+ syllabus
+
+
+ textbooks
+
+
+ evaluation
+
+
+ instructor cv
+
+
+ tcenter
+
+
+
+
+
+
+
+
+ Course Title:
+
+
+ Understanding Theatre
+
+
+
+
+ Class Info:
+
+
+
+
+
+
+ Class Section:
+
+
+ THEA1310.001.25S
+
+
+ Instruction Mode:
+
+
+ Face-to-Face
+
+
+
+
+ Class Level:
+
+
+ Undergraduate
+
+
+ Activity Type:
+
+
+ Lecture
+
+
+
+
+ Semester Credit Hours:
+
+
+ 3
+
+
+ Class/Course Number:
+
+
+ 24043 / 003909
+
+
+
+
+ Grading:
+
+
+ Graded - Undergraduate
+
+
+ Session Type:
+
+
+ Regular Academic Session
+
+
+
+
+ Add Consent:
+
+
+ No Consent
+
+
+ Orion Date/Time:
+
+
+ 2025-08-28 06:30:01
+
+
+
+
+ How often a course is scheduled:
+
+
+ Once Each Long Semester
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Status:
+
+
+ Enrollment Status: OPEN Available Seats: 15 Enrolled
+ Total: 33 Waitlist: 0
+
+
+
+
+
+ Description:
+
+
+ THEA 1310 - Understanding Theatre (3
+ semester credit hours) Lectures, discussions, and performances designed to explore artistic,
+ philosophical, social, historical, and psychological dimensions of the theatrical experience. Topics
+ may include analysis of scripts, the nature of the theater compared to the other performing arts,
+ and the nature of popular entertainments. (3-0) S
+
+
+
+
+
+
+
+
+ Instructor(s):
+
+
+
+
+
+ TA/RA(s):
+ (none)
+
+
+
+ Schedule:
+
+
+
+
Class Location and Times
+
Term: 25SType: Regular Academic SessionStarts:
+ January 21, 2025Ends: May 16, 2025
+
+
+
+ January 21, 2025-May 9, 2025
+ Monday, Wednesday
+ 10:00am-11:15am
+ FO 1.202
+
+
+
+
FO Building Floor 1 - Room 1.202
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Core:
+ Texas Core Areas 050+090 - Creative Arts + CAO
+
+
+
+ College:
+
+
+ School of Arts, Humanities, and
+ Technology
+
+
+
+
+
+ Syllabus:
+
+
+ Syllabus for Understanding Theatre
+ (THEA1310.001.25S)
+
+
+
+
+
+ Evaluation:
+
+
+ Class evaluation for
+ Understanding Theatre (THEA1310.001.25S)
+
+
+
+
+
+
+ Index:
+
+
+ 001 1310 2025 2252 003909 1310001 2021536849 25s active_y activitytype_lecture agrp_ahtc aorg_ahtc
+ attr_catl attr_cbrt attr_clvl attr_core attr_ctbk building_fo catl_degreq cbrt_complete clevel_u
+ clevel_ugrd clvl_1 cnum_1310 cnum_24043 col_aht consent_n core_ core_- core_050 core_050090 core_090
+ core_areas core_arts core_cao core_core core_creative core_texas cp_thea credreq_y crseid_003909
+ csec_001 csess_1 cstat_a ctbk_complete ctype_e damian day_monday day_wednesday days_mw dept_ahtc
+ dept_spau displayschedule_y dpe190000 ecap_48 emplid_2021536849 enyaosah estat_o etotal_33
+ evalstatus_reported facility_fo1202 filldecile_1 filldecile_2 filldecile_3 filldecile_4 filldecile_5
+ filldecile_6 filldecile_7 floor_1 hasassistant_n hasenrollment_y hasevaluation_y hasinstructor_y
+ haspcv_y hassyllabus_y hastextbooks_y haswaitlist_n holiday_dalhol instructionmode_face-to-face
+ location_utdm netid_dpe190000 psec_001 room_1202 scheduleprint_y seatstaken_y
+ sectionaddress_thea131000125s sectionstatus_a sessioncode_1 site_utdm specialperms_y startminute_600
+ stopminute_675 strm_2252 term_25s thea thea1310 thea131000125s theatre understanding waitlist_n
+ wcap_10 xlist_n year_2025
+
+
+
+
+
+
+
+
diff --git a/parser/testdata/case_006/professors.json b/parser/testdata/case_006/professors.json
new file mode 100644
index 0000000..8860703
--- /dev/null
+++ b/parser/testdata/case_006/professors.json
@@ -0,0 +1,23 @@
+[
+ {
+ "_id": "6972d72a922d8b0bc3a3bce2",
+ "first_name": "Damian",
+ "last_name": "Enyaosah",
+ "titles": [
+ "Primary Instructor"
+ ],
+ "email": "dpe190000@utdallas.edu",
+ "phone_number": "",
+ "office": {
+ "building": "",
+ "room": "",
+ "map_uri": ""
+ },
+ "profile_uri": "",
+ "image_uri": "",
+ "office_hours": null,
+ "sections": [
+ "6972d72a922d8b0bc3a3bce1"
+ ]
+ }
+]
diff --git a/parser/testdata/case_006/section.json b/parser/testdata/case_006/section.json
new file mode 100644
index 0000000..5e58473
--- /dev/null
+++ b/parser/testdata/case_006/section.json
@@ -0,0 +1,61 @@
+{
+ "_id": "6972d72a922d8b0bc3a3bce1",
+ "section_number": "001",
+ "course_reference": "6972d72a922d8b0bc3a3bce0",
+ "section_corequisites": null,
+ "academic_session": {
+ "name": "25S",
+ "start_date": "2025-01-21T00:00:00-06:00",
+ "end_date": "2025-05-16T00:00:00-05:00"
+ },
+ "professors": [
+ "6972d72a922d8b0bc3a3bce2"
+ ],
+ "teaching_assistants": [],
+ "internal_class_number": "24043",
+ "instruction_mode": "Face-to-Face",
+ "meetings": [
+ {
+ "start_date": "2025-01-21T00:00:00-06:00",
+ "end_date": "2025-05-09T00:00:00-05:00",
+ "meeting_days": [
+ "Monday",
+ "Wednesday"
+ ],
+ "start_time": "10:00am",
+ "end_time": "11:15am",
+ "modality": "",
+ "location": {
+ "building": "FO",
+ "room": "1.202",
+ "map_uri": "https://locator.utdallas.edu/FO_1.202"
+ }
+ }
+ ],
+ "core_flags": [
+ "050",
+ "090"
+ ],
+ "syllabus_uri": "https://dox.utdallas.edu/syl154931",
+ "grade_distribution": [
+ 4,
+ 7,
+ 9,
+ 3,
+ 1,
+ 1,
+ 0,
+ 1,
+ 2,
+ 0,
+ 0,
+ 0,
+ 5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "attributes": null
+}
From 9040f94742b13c3c07dfb2e246c4070cc878b5c0 Mon Sep 17 00:00:00 2001
From: SoggyRihno <94922205+SoggyRihno@users.noreply.github.com>
Date: Thu, 22 Jan 2026 22:26:55 -0600
Subject: [PATCH 4/5] Added unit tests for gradeLoader.go, added more test
data, minor fix validator test
---
build.bat | 4 +-
parser/gradeLoader.go | 4 +
parser/gradeLoader_test.go | 126 +++++++++++++++++-
parser/testdata/case_000/course.json | 4 +-
parser/testdata/case_000/professors.json | 8 +-
parser/testdata/case_000/section.json | 29 +++-
parser/testdata/case_001/course.json | 4 +-
parser/testdata/case_001/professors.json | 8 +-
parser/testdata/case_001/section.json | 29 +++-
parser/testdata/case_002/course.json | 4 +-
parser/testdata/case_002/professors.json | 4 +-
parser/testdata/case_002/section.json | 27 +++-
parser/testdata/case_003/course.json | 4 +-
parser/testdata/case_003/professors.json | 4 +-
parser/testdata/case_003/section.json | 6 +-
parser/testdata/case_004/course.json | 4 +-
parser/testdata/case_004/section.json | 4 +-
parser/testdata/case_005/course.json | 4 +-
parser/testdata/case_005/section.json | 4 +-
parser/testdata/case_006/classInfo.json | 24 ++--
parser/testdata/case_006/course.json | 46 +++----
parser/testdata/case_006/professors.json | 4 +-
parser/testdata/case_006/section.json | 118 ++++++++---------
parser/testdata/courses.json | 47 +++++--
parser/testdata/professors.json | 67 ++++++----
parser/testdata/sections.json | 160 ++++++++++++++++++++---
parser/validator_test.go | 15 ++-
scrapers/coursebook.go | 2 +-
28 files changed, 558 insertions(+), 206 deletions(-)
diff --git a/build.bat b/build.bat
index 9a8003a..8d671d1 100644
--- a/build.bat
+++ b/build.bat
@@ -24,8 +24,8 @@ echo Performing checks...
go mod tidy && ^
go vet ./... && ^
staticcheck ./... && ^
-gofmt -w ./.. && ^
-goimports -w ./..
+gofmt -w . && ^
+goimports -w .
if ERRORLEVEL 1 exit /b %ERRORLEVEL% :: fail if error occurred
echo Checks done!
if %skip%==1 exit
diff --git a/parser/gradeLoader.go b/parser/gradeLoader.go
index 43e4855..f92a7f0 100644
--- a/parser/gradeLoader.go
+++ b/parser/gradeLoader.go
@@ -58,6 +58,10 @@ func csvToMap(filename string) (map[string][]int, error) {
return nil, fmt.Errorf("error parsing %s: %v", filename, err)
}
+ if len(records) == 0 {
+ return nil, fmt.Errorf("empty CSV file '%s'", filename)
+ }
+
indexMap := make(map[string]int)
for j, col := range records[0] {
switch col {
diff --git a/parser/gradeLoader_test.go b/parser/gradeLoader_test.go
index b7a414b..efb3e1c 100644
--- a/parser/gradeLoader_test.go
+++ b/parser/gradeLoader_test.go
@@ -1,14 +1,134 @@
package parser
import (
+ "fmt"
+ "os"
+ "path/filepath"
"testing"
+
+ "github.com/google/go-cmp/cmp"
+)
+
+var (
+ gradeLoaderTestCases = map[string]struct {
+ csvContent string
+ want map[string][]int
+ fail bool
+ }{
+ "Valid_Data": {
+ csvContent: `Instructor 1,Instructor 2,Instructor 3,Instructor 4,Instructor 5,Instructor 6,Subject,"Catalog Nbr",Section,A+,A,A-,B+,B,B-,C+,C,C-,D+,D,D-,F,NF,CR,I,NC,P,W
+"Curchack, Fred",,,,,,AP,3300,501,6,4,2,2,1,3,1,1,,,,,1,,,,,,0
+"Anjum, Zafar",,,,,,ARAB,1311,001,,26,,,1,,,,,,,,,,,,,,2`,
+ want: map[string][]int{
+ "AP3300501": {6, 4, 2, 2, 1, 3, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0},
+ "ARAB13111": {0, 26, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0},
+ },
+ fail: false,
+ },
+ "Missing_Required_Column_A+": {
+ csvContent: `Subject,"Catalog Nbr",Section,A,A-,B+
+CS,1337,001,10,5,5`,
+ fail: true,
+ },
+ "Missing_Required_Column_Subject": {
+ csvContent: `Instructor,"Catalog Nbr",Section,A+,A
+Doe,1337,001,10,5`,
+ fail: true,
+ },
+ "Empty_File": {
+ csvContent: ``,
+ fail: true,
+ },
+ }
)
func TestLoadGrades(t *testing.T) {
- _, err := loadGrades("../grade-data/")
- if err != nil {
- t.Errorf("loadGrades() error = %v", err)
+ invalidCSVNames := []string{"22", "2F", "2022F", "20-U", "15Fall"}
+
+ for i, name := range invalidCSVNames {
+ t.Run(
+ fmt.Sprintf("Invalid_CSV_Name_%d", i), func(t *testing.T) {
+ tempDir := t.TempDir()
+
+ temp, err := os.Create(filepath.Join(tempDir, name+".csv"))
+ if err != nil {
+ t.Errorf("failed to create temp file: %v", err)
+ }
+ defer temp.Close()
+
+ _, err = loadGrades(tempDir)
+ if err == nil {
+ t.Errorf("expected error but got none")
+ }
+ },
+ )
+ }
+
+ validCSVNames := []string{"25F", "18U", "26S"}
+ for i, name := range validCSVNames {
+ t.Run(
+ fmt.Sprintf("Valid_CSV_Name_%d", i), func(t *testing.T) {
+ tempDir := t.TempDir()
+
+ temp, err := os.Create(filepath.Join(tempDir, name+".csv"))
+ if err != nil {
+ t.Errorf("failed to create temp file: %v", err)
+ }
+ defer temp.Close()
+
+ _, err = temp.WriteString(gradeLoaderTestCases["Valid_Data"].csvContent)
+ if err != nil {
+ t.Errorf("failed to write test data: %v", err)
+ }
+
+ _, err = loadGrades(tempDir)
+ if err != nil {
+ t.Errorf("valid .csv failed: %v", err)
+ }
+ },
+ )
}
+ t.Run("Real_Data", func(t *testing.T) {
+ _, err := loadGrades("../grade-data/")
+ if err != nil {
+ t.Errorf("failed to load grades: %v", err)
+ }
+ })
+}
+
+func TestCSVToMap(t *testing.T) {
+ tempDir := t.TempDir()
+
+ for name, testCase := range gradeLoaderTestCases {
+ t.Run(name, func(t *testing.T) {
+
+ temp, err := os.CreateTemp(tempDir, "grades*.csv")
+ if err != nil {
+ t.Errorf("failed to create temp file: %v", err)
+ }
+ defer temp.Close()
+
+ if _, err = temp.WriteString(testCase.csvContent); err != nil {
+ t.Errorf("failed to write test data: %v", err)
+ }
+
+ output, err := csvToMap(temp.Name())
+ if err != nil {
+ if testCase.fail {
+ return
+ }
+ t.Errorf("failed to load csv: %v", err)
+ } else if testCase.fail {
+ t.Errorf("expected failure but got none")
+ } else {
+ diff := cmp.Diff(testCase.want, output)
+ if diff != "" {
+ t.Errorf("Failed (-expected +got)\n %s", diff)
+ }
+ }
+
+ })
+ }
}
diff --git a/parser/testdata/case_000/course.json b/parser/testdata/case_000/course.json
index 5e342be..e050494 100644
--- a/parser/testdata/case_000/course.json
+++ b/parser/testdata/case_000/course.json
@@ -1,5 +1,5 @@
{
- "_id": "67d07ee0c972c18731e23bd7",
+ "_id": "6972f54d6afb10b361a3e8b1",
"subject_prefix": "ACCT",
"course_number": "2301",
"title": "Introductory Financial Accounting",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23bd8"
+ "6972f54d6afb10b361a3e8b2"
],
"lecture_contact_hours": "3",
"laboratory_contact_hours": "0",
diff --git a/parser/testdata/case_000/professors.json b/parser/testdata/case_000/professors.json
index 207c908..57aec29 100644
--- a/parser/testdata/case_000/professors.json
+++ b/parser/testdata/case_000/professors.json
@@ -1,6 +1,6 @@
[
{
- "_id": "67d07ee0c972c18731e23bd9",
+ "_id": "6972f54d6afb10b361a3e8b3",
"first_name": "Naim Bugra",
"last_name": "Ozel",
"titles": [
@@ -17,11 +17,11 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "67d07ee0c972c18731e23bd8"
+ "6972f54d6afb10b361a3e8b2"
]
},
{
- "_id": "67d07ee0c972c18731e23bda",
+ "_id": "6972f54d6afb10b361a3e8b4",
"first_name": "Jieying",
"last_name": "Zhang",
"titles": [
@@ -38,7 +38,7 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "67d07ee0c972c18731e23bd8"
+ "6972f54d6afb10b361a3e8b2"
]
}
]
diff --git a/parser/testdata/case_000/section.json b/parser/testdata/case_000/section.json
index a67c0f5..b2faa8f 100644
--- a/parser/testdata/case_000/section.json
+++ b/parser/testdata/case_000/section.json
@@ -1,7 +1,7 @@
{
- "_id": "67d07ee0c972c18731e23bd8",
+ "_id": "6972f54d6afb10b361a3e8b2",
"section_number": "003",
- "course_reference": "67d07ee0c972c18731e23bd7",
+ "course_reference": "6972f54d6afb10b361a3e8b1",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -9,8 +9,8 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "67d07ee0c972c18731e23bd9",
- "67d07ee0c972c18731e23bda"
+ "6972f54d6afb10b361a3e8b3",
+ "6972f54d6afb10b361a3e8b4"
],
"teaching_assistants": [
{
@@ -48,6 +48,25 @@
],
"core_flags": [],
"syllabus_uri": "https://dox.utdallas.edu/syl152555",
- "grade_distribution": [],
+ "grade_distribution": [
+ 9,
+ 9,
+ 4,
+ 6,
+ 4,
+ 5,
+ 12,
+ 3,
+ 1,
+ 3,
+ 1,
+ 0,
+ 4,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
"attributes": null
}
diff --git a/parser/testdata/case_001/course.json b/parser/testdata/case_001/course.json
index 24dcf8b..4a015f2 100644
--- a/parser/testdata/case_001/course.json
+++ b/parser/testdata/case_001/course.json
@@ -1,5 +1,5 @@
{
- "_id": "67d07ee0c972c18731e23bdb",
+ "_id": "6972f54d6afb10b361a3e8b5",
"subject_prefix": "ACCT",
"course_number": "2301",
"title": "Introductory Financial Accounting",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23bdc"
+ "6972f54d6afb10b361a3e8b6"
],
"lecture_contact_hours": "3",
"laboratory_contact_hours": "0",
diff --git a/parser/testdata/case_001/professors.json b/parser/testdata/case_001/professors.json
index 8bf8c6b..72111e2 100644
--- a/parser/testdata/case_001/professors.json
+++ b/parser/testdata/case_001/professors.json
@@ -1,6 +1,6 @@
[
{
- "_id": "67d07ee0c972c18731e23bdd",
+ "_id": "6972f54d6afb10b361a3e8b7",
"first_name": "Jieying",
"last_name": "Zhang",
"titles": [
@@ -17,11 +17,11 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "67d07ee0c972c18731e23bdc"
+ "6972f54d6afb10b361a3e8b6"
]
},
{
- "_id": "67d07ee0c972c18731e23bde",
+ "_id": "6972f54d6afb10b361a3e8b8",
"first_name": "Naim Bugra",
"last_name": "Ozel",
"titles": [
@@ -38,7 +38,7 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "67d07ee0c972c18731e23bdc"
+ "6972f54d6afb10b361a3e8b6"
]
}
]
diff --git a/parser/testdata/case_001/section.json b/parser/testdata/case_001/section.json
index eeb9360..4b10fcf 100644
--- a/parser/testdata/case_001/section.json
+++ b/parser/testdata/case_001/section.json
@@ -1,7 +1,7 @@
{
- "_id": "67d07ee0c972c18731e23bdc",
+ "_id": "6972f54d6afb10b361a3e8b6",
"section_number": "001",
- "course_reference": "67d07ee0c972c18731e23bdb",
+ "course_reference": "6972f54d6afb10b361a3e8b5",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -9,8 +9,8 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "67d07ee0c972c18731e23bdd",
- "67d07ee0c972c18731e23bde"
+ "6972f54d6afb10b361a3e8b7",
+ "6972f54d6afb10b361a3e8b8"
],
"teaching_assistants": [
{
@@ -48,6 +48,25 @@
],
"core_flags": [],
"syllabus_uri": "https://dox.utdallas.edu/syl152552",
- "grade_distribution": [],
+ "grade_distribution": [
+ 2,
+ 7,
+ 5,
+ 8,
+ 8,
+ 7,
+ 3,
+ 6,
+ 1,
+ 3,
+ 3,
+ 0,
+ 9,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
"attributes": null
}
diff --git a/parser/testdata/case_002/course.json b/parser/testdata/case_002/course.json
index 141ff6b..1db1746 100644
--- a/parser/testdata/case_002/course.json
+++ b/parser/testdata/case_002/course.json
@@ -1,5 +1,5 @@
{
- "_id": "67d07ee0c972c18731e23bdf",
+ "_id": "6972f54d6afb10b361a3e8b9",
"subject_prefix": "BA",
"course_number": "1320",
"title": "Business in a Global World",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23be0"
+ "6972f54d6afb10b361a3e8ba"
],
"lecture_contact_hours": "3",
"laboratory_contact_hours": "0",
diff --git a/parser/testdata/case_002/professors.json b/parser/testdata/case_002/professors.json
index c6913f6..1f17b80 100644
--- a/parser/testdata/case_002/professors.json
+++ b/parser/testdata/case_002/professors.json
@@ -1,6 +1,6 @@
[
{
- "_id": "67d07ee0c972c18731e23be1",
+ "_id": "6972f54d6afb10b361a3e8bb",
"first_name": "Peter",
"last_name": "Lewin",
"titles": [
@@ -17,7 +17,7 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "67d07ee0c972c18731e23be0"
+ "6972f54d6afb10b361a3e8ba"
]
}
]
diff --git a/parser/testdata/case_002/section.json b/parser/testdata/case_002/section.json
index 6eb44f5..4ca35d9 100644
--- a/parser/testdata/case_002/section.json
+++ b/parser/testdata/case_002/section.json
@@ -1,7 +1,7 @@
{
- "_id": "67d07ee0c972c18731e23be0",
+ "_id": "6972f54d6afb10b361a3e8ba",
"section_number": "501",
- "course_reference": "67d07ee0c972c18731e23bdf",
+ "course_reference": "6972f54d6afb10b361a3e8b9",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -9,7 +9,7 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "67d07ee0c972c18731e23be1"
+ "6972f54d6afb10b361a3e8bb"
],
"teaching_assistants": [
{
@@ -44,6 +44,25 @@
"090"
],
"syllabus_uri": "https://dox.utdallas.edu/syl153033",
- "grade_distribution": [],
+ "grade_distribution": [
+ 0,
+ 13,
+ 23,
+ 6,
+ 4,
+ 7,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
"attributes": null
}
diff --git a/parser/testdata/case_003/course.json b/parser/testdata/case_003/course.json
index 94219f8..0111554 100644
--- a/parser/testdata/case_003/course.json
+++ b/parser/testdata/case_003/course.json
@@ -1,5 +1,5 @@
{
- "_id": "67d07ee0c972c18731e23be2",
+ "_id": "6972f54d6afb10b361a3e8bc",
"subject_prefix": "BIOL",
"course_number": "6111",
"title": "Graduate Research Presentation",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23be3"
+ "6972f54d6afb10b361a3e8bd"
],
"lecture_contact_hours": "1",
"laboratory_contact_hours": "0",
diff --git a/parser/testdata/case_003/professors.json b/parser/testdata/case_003/professors.json
index 3cb4a51..e921077 100644
--- a/parser/testdata/case_003/professors.json
+++ b/parser/testdata/case_003/professors.json
@@ -1,6 +1,6 @@
[
{
- "_id": "67d07ee0c972c18731e23be4",
+ "_id": "6972f54d6afb10b361a3e8be",
"first_name": "Tian",
"last_name": "Hong",
"titles": [
@@ -17,7 +17,7 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "67d07ee0c972c18731e23be3"
+ "6972f54d6afb10b361a3e8bd"
]
}
]
diff --git a/parser/testdata/case_003/section.json b/parser/testdata/case_003/section.json
index fff4105..d598a10 100644
--- a/parser/testdata/case_003/section.json
+++ b/parser/testdata/case_003/section.json
@@ -1,7 +1,7 @@
{
- "_id": "67d07ee0c972c18731e23be3",
+ "_id": "6972f54d6afb10b361a3e8bd",
"section_number": "016",
- "course_reference": "67d07ee0c972c18731e23be2",
+ "course_reference": "6972f54d6afb10b361a3e8bc",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -9,7 +9,7 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "67d07ee0c972c18731e23be4"
+ "6972f54d6afb10b361a3e8be"
],
"teaching_assistants": [],
"internal_class_number": "29611",
diff --git a/parser/testdata/case_004/course.json b/parser/testdata/case_004/course.json
index d8c5383..b253341 100644
--- a/parser/testdata/case_004/course.json
+++ b/parser/testdata/case_004/course.json
@@ -1,5 +1,5 @@
{
- "_id": "67d07ee0c972c18731e23be5",
+ "_id": "6972f54d6afb10b361a3e8bf",
"subject_prefix": "AERO",
"course_number": "3320",
"title": "- Recitation",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23be6"
+ "6972f54d6afb10b361a3e8c0"
],
"lecture_contact_hours": "",
"laboratory_contact_hours": "",
diff --git a/parser/testdata/case_004/section.json b/parser/testdata/case_004/section.json
index 2481524..0608366 100644
--- a/parser/testdata/case_004/section.json
+++ b/parser/testdata/case_004/section.json
@@ -1,7 +1,7 @@
{
- "_id": "67d07ee0c972c18731e23be6",
+ "_id": "6972f54d6afb10b361a3e8c0",
"section_number": "201",
- "course_reference": "67d07ee0c972c18731e23be5",
+ "course_reference": "6972f54d6afb10b361a3e8bf",
"section_corequisites": null,
"academic_session": {
"name": "25S",
diff --git a/parser/testdata/case_005/course.json b/parser/testdata/case_005/course.json
index 9095afc..209bd70 100644
--- a/parser/testdata/case_005/course.json
+++ b/parser/testdata/case_005/course.json
@@ -1,5 +1,5 @@
{
- "_id": "67d07ee0c972c18731e23be7",
+ "_id": "6972f54d6afb10b361a3e8c1",
"subject_prefix": "AERO",
"course_number": "4320",
"title": "- Laboratory",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23be8"
+ "6972f54d6afb10b361a3e8c2"
],
"lecture_contact_hours": "",
"laboratory_contact_hours": "",
diff --git a/parser/testdata/case_005/section.json b/parser/testdata/case_005/section.json
index 712c972..247ab4c 100644
--- a/parser/testdata/case_005/section.json
+++ b/parser/testdata/case_005/section.json
@@ -1,7 +1,7 @@
{
- "_id": "67d07ee0c972c18731e23be8",
+ "_id": "6972f54d6afb10b361a3e8c2",
"section_number": "002",
- "course_reference": "67d07ee0c972c18731e23be7",
+ "course_reference": "6972f54d6afb10b361a3e8c1",
"section_corequisites": null,
"academic_session": {
"name": "25S",
diff --git a/parser/testdata/case_006/classInfo.json b/parser/testdata/case_006/classInfo.json
index ae81d1c..34e725a 100644
--- a/parser/testdata/case_006/classInfo.json
+++ b/parser/testdata/case_006/classInfo.json
@@ -1,14 +1,14 @@
{
- "": "",
- "Activity Type:": "Lecture",
- "Add Consent:": "No Consent",
- "Class Level:": "Undergraduate",
- "Class Section:": "THEA1310.001.25S",
- "Class/Course Number:": "24043 / 003909",
- "Grading:": "Graded - Undergraduate",
- "How often a course is scheduled:": "Once Each Long Semester",
- "Instruction Mode:": "Face-to-Face",
- "Orion Date/Time:": "2025-08-28 06:30:01",
- "Semester Credit Hours:": "3",
- "Session Type:": "Regular Academic Session"
+ "": "",
+ "Activity Type:": "Lecture",
+ "Add Consent:": "No Consent",
+ "Class Level:": "Undergraduate",
+ "Class Section:": "THEA1310.001.25S",
+ "Class/Course Number:": "24043 / 003909",
+ "Grading:": "Graded - Undergraduate",
+ "How often a course is scheduled:": "Once Each Long Semester",
+ "Instruction Mode:": "Face-to-Face",
+ "Orion Date/Time:": "2025-08-28 06:30:01",
+ "Semester Credit Hours:": "3",
+ "Session Type:": "Regular Academic Session"
}
diff --git a/parser/testdata/case_006/course.json b/parser/testdata/case_006/course.json
index 26de202..a20be42 100644
--- a/parser/testdata/case_006/course.json
+++ b/parser/testdata/case_006/course.json
@@ -1,25 +1,25 @@
{
- "_id": "6972d72a922d8b0bc3a3bce0",
- "subject_prefix": "THEA",
- "course_number": "1310",
- "title": "Understanding Theatre",
- "description": "THEA 1310 - Understanding Theatre (3 semester credit hours) Lectures, discussions, and performances designed to explore artistic, philosophical, social, historical, and psychological dimensions of the theatrical experience. Topics may include analysis of scripts, the nature of the theater compared to the other performing arts, and the nature of popular entertainments. (3-0) S",
- "enrollment_reqs": "",
- "school": "School of Arts, Humanities, and Technology",
- "credit_hours": "3",
- "class_level": "Undergraduate",
- "activity_type": "Lecture",
- "grading": "Graded - Undergraduate",
- "internal_course_number": "003909",
- "prerequisites": null,
- "corequisites": null,
- "co_or_pre_requisites": null,
- "sections": [
- "6972d72a922d8b0bc3a3bce1"
- ],
- "lecture_contact_hours": "3",
- "laboratory_contact_hours": "0",
- "offering_frequency": "S",
- "catalog_year": "24",
- "attributes": null
+ "_id": "6972f54d6afb10b361a3e8c3",
+ "subject_prefix": "THEA",
+ "course_number": "1310",
+ "title": "Understanding Theatre",
+ "description": "THEA 1310 - Understanding Theatre (3 semester credit hours) Lectures, discussions, and performances designed to explore artistic, philosophical, social, historical, and psychological dimensions of the theatrical experience. Topics may include analysis of scripts, the nature of the theater compared to the other performing arts, and the nature of popular entertainments. (3-0) S",
+ "enrollment_reqs": "",
+ "school": "School of Arts, Humanities, and Technology",
+ "credit_hours": "3",
+ "class_level": "Undergraduate",
+ "activity_type": "Lecture",
+ "grading": "Graded - Undergraduate",
+ "internal_course_number": "003909",
+ "prerequisites": null,
+ "corequisites": null,
+ "co_or_pre_requisites": null,
+ "sections": [
+ "6972f54d6afb10b361a3e8c4"
+ ],
+ "lecture_contact_hours": "3",
+ "laboratory_contact_hours": "0",
+ "offering_frequency": "S",
+ "catalog_year": "24",
+ "attributes": null
}
diff --git a/parser/testdata/case_006/professors.json b/parser/testdata/case_006/professors.json
index 8860703..bfde863 100644
--- a/parser/testdata/case_006/professors.json
+++ b/parser/testdata/case_006/professors.json
@@ -1,6 +1,6 @@
[
{
- "_id": "6972d72a922d8b0bc3a3bce2",
+ "_id": "6972f54d6afb10b361a3e8c5",
"first_name": "Damian",
"last_name": "Enyaosah",
"titles": [
@@ -17,7 +17,7 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972d72a922d8b0bc3a3bce1"
+ "6972f54d6afb10b361a3e8c4"
]
}
]
diff --git a/parser/testdata/case_006/section.json b/parser/testdata/case_006/section.json
index 5e58473..2c57733 100644
--- a/parser/testdata/case_006/section.json
+++ b/parser/testdata/case_006/section.json
@@ -1,61 +1,61 @@
{
- "_id": "6972d72a922d8b0bc3a3bce1",
- "section_number": "001",
- "course_reference": "6972d72a922d8b0bc3a3bce0",
- "section_corequisites": null,
- "academic_session": {
- "name": "25S",
- "start_date": "2025-01-21T00:00:00-06:00",
- "end_date": "2025-05-16T00:00:00-05:00"
- },
- "professors": [
- "6972d72a922d8b0bc3a3bce2"
- ],
- "teaching_assistants": [],
- "internal_class_number": "24043",
- "instruction_mode": "Face-to-Face",
- "meetings": [
- {
- "start_date": "2025-01-21T00:00:00-06:00",
- "end_date": "2025-05-09T00:00:00-05:00",
- "meeting_days": [
- "Monday",
- "Wednesday"
- ],
- "start_time": "10:00am",
- "end_time": "11:15am",
- "modality": "",
- "location": {
- "building": "FO",
- "room": "1.202",
- "map_uri": "https://locator.utdallas.edu/FO_1.202"
- }
- }
- ],
- "core_flags": [
- "050",
- "090"
- ],
- "syllabus_uri": "https://dox.utdallas.edu/syl154931",
- "grade_distribution": [
- 4,
- 7,
- 9,
- 3,
- 1,
- 1,
- 0,
- 1,
- 2,
- 0,
- 0,
- 0,
- 5,
- 0,
- 0,
- 0,
- 0,
- 0
- ],
- "attributes": null
+ "_id": "6972f54d6afb10b361a3e8c4",
+ "section_number": "001",
+ "course_reference": "6972f54d6afb10b361a3e8c3",
+ "section_corequisites": null,
+ "academic_session": {
+ "name": "25S",
+ "start_date": "2025-01-21T00:00:00-06:00",
+ "end_date": "2025-05-16T00:00:00-05:00"
+ },
+ "professors": [
+ "6972f54d6afb10b361a3e8c5"
+ ],
+ "teaching_assistants": [],
+ "internal_class_number": "24043",
+ "instruction_mode": "Face-to-Face",
+ "meetings": [
+ {
+ "start_date": "2025-01-21T00:00:00-06:00",
+ "end_date": "2025-05-09T00:00:00-05:00",
+ "meeting_days": [
+ "Monday",
+ "Wednesday"
+ ],
+ "start_time": "10:00am",
+ "end_time": "11:15am",
+ "modality": "",
+ "location": {
+ "building": "FO",
+ "room": "1.202",
+ "map_uri": "https://locator.utdallas.edu/FO_1.202"
+ }
+ }
+ ],
+ "core_flags": [
+ "050",
+ "090"
+ ],
+ "syllabus_uri": "https://dox.utdallas.edu/syl154931",
+ "grade_distribution": [
+ 4,
+ 7,
+ 9,
+ 3,
+ 1,
+ 1,
+ 0,
+ 1,
+ 2,
+ 0,
+ 0,
+ 0,
+ 5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "attributes": null
}
diff --git a/parser/testdata/courses.json b/parser/testdata/courses.json
index 78ae4d4..df9144e 100644
--- a/parser/testdata/courses.json
+++ b/parser/testdata/courses.json
@@ -1,6 +1,6 @@
[
{
- "_id": "67d07ee0c972c18731e23bee",
+ "_id": "6972f54d6afb10b361a3e8cb",
"subject_prefix": "BA",
"course_number": "1320",
"title": "Business in a Global World",
@@ -16,7 +16,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23bef"
+ "6972f54d6afb10b361a3e8cc"
],
"lecture_contact_hours": "3",
"laboratory_contact_hours": "0",
@@ -25,7 +25,7 @@
"attributes": null
},
{
- "_id": "67d07ee0c972c18731e23bf1",
+ "_id": "6972f54d6afb10b361a3e8ce",
"subject_prefix": "BIOL",
"course_number": "6111",
"title": "Graduate Research Presentation",
@@ -41,7 +41,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23bf2"
+ "6972f54d6afb10b361a3e8cf"
],
"lecture_contact_hours": "1",
"laboratory_contact_hours": "0",
@@ -50,7 +50,7 @@
"attributes": null
},
{
- "_id": "67d07ee0c972c18731e23bf4",
+ "_id": "6972f54d6afb10b361a3e8d1",
"subject_prefix": "AERO",
"course_number": "3320",
"title": "- Recitation",
@@ -66,7 +66,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23bf5"
+ "6972f54d6afb10b361a3e8d2"
],
"lecture_contact_hours": "",
"laboratory_contact_hours": "",
@@ -75,7 +75,7 @@
"attributes": null
},
{
- "_id": "67d07ee0c972c18731e23bf6",
+ "_id": "6972f54d6afb10b361a3e8d3",
"subject_prefix": "AERO",
"course_number": "4320",
"title": "- Laboratory",
@@ -91,7 +91,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23bf7"
+ "6972f54d6afb10b361a3e8d4"
],
"lecture_contact_hours": "",
"laboratory_contact_hours": "",
@@ -100,7 +100,32 @@
"attributes": null
},
{
- "_id": "67d07ee0c972c18731e23be9",
+ "_id": "6972f54d6afb10b361a3e8d5",
+ "subject_prefix": "THEA",
+ "course_number": "1310",
+ "title": "Understanding Theatre",
+ "description": "THEA 1310 - Understanding Theatre (3 semester credit hours) Lectures, discussions, and performances designed to explore artistic, philosophical, social, historical, and psychological dimensions of the theatrical experience. Topics may include analysis of scripts, the nature of the theater compared to the other performing arts, and the nature of popular entertainments. (3-0) S",
+ "enrollment_reqs": "",
+ "school": "School of Arts, Humanities, and Technology",
+ "credit_hours": "3",
+ "class_level": "Undergraduate",
+ "activity_type": "Lecture",
+ "grading": "Graded - Undergraduate",
+ "internal_course_number": "003909",
+ "prerequisites": null,
+ "corequisites": null,
+ "co_or_pre_requisites": null,
+ "sections": [
+ "6972f54d6afb10b361a3e8d6"
+ ],
+ "lecture_contact_hours": "3",
+ "laboratory_contact_hours": "0",
+ "offering_frequency": "S",
+ "catalog_year": "24",
+ "attributes": null
+ },
+ {
+ "_id": "6972f54d6afb10b361a3e8c6",
"subject_prefix": "ACCT",
"course_number": "2301",
"title": "Introductory Financial Accounting",
@@ -116,8 +141,8 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "67d07ee0c972c18731e23bea",
- "67d07ee0c972c18731e23bed"
+ "6972f54d6afb10b361a3e8c7",
+ "6972f54d6afb10b361a3e8ca"
],
"lecture_contact_hours": "3",
"laboratory_contact_hours": "0",
diff --git a/parser/testdata/professors.json b/parser/testdata/professors.json
index 2a931c4..dc66b36 100644
--- a/parser/testdata/professors.json
+++ b/parser/testdata/professors.json
@@ -1,12 +1,12 @@
[
{
- "_id": "67d07ee0c972c18731e23beb",
- "first_name": "Naim Bugra",
- "last_name": "Ozel",
+ "_id": "6972f54d6afb10b361a3e8c9",
+ "first_name": "Jieying",
+ "last_name": "Zhang",
"titles": [
"Primary Instructor (50%)"
],
- "email": "nbo150030@utdallas.edu",
+ "email": "jxz146230@utdallas.edu",
"phone_number": "",
"office": {
"building": "",
@@ -17,18 +17,18 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "67d07ee0c972c18731e23bea",
- "67d07ee0c972c18731e23bed"
+ "6972f54d6afb10b361a3e8c7",
+ "6972f54d6afb10b361a3e8ca"
]
},
{
- "_id": "67d07ee0c972c18731e23bec",
- "first_name": "Jieying",
- "last_name": "Zhang",
+ "_id": "6972f54d6afb10b361a3e8cd",
+ "first_name": "Peter",
+ "last_name": "Lewin",
"titles": [
- "Primary Instructor (50%)"
+ "Primary Instructor"
],
- "email": "jxz146230@utdallas.edu",
+ "email": "plewin@utdallas.edu",
"phone_number": "",
"office": {
"building": "",
@@ -39,18 +39,17 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "67d07ee0c972c18731e23bea",
- "67d07ee0c972c18731e23bed"
+ "6972f54d6afb10b361a3e8cc"
]
},
{
- "_id": "67d07ee0c972c18731e23bf0",
- "first_name": "Peter",
- "last_name": "Lewin",
+ "_id": "6972f54d6afb10b361a3e8d0",
+ "first_name": "Tian",
+ "last_name": "Hong",
"titles": [
"Primary Instructor"
],
- "email": "plewin@utdallas.edu",
+ "email": "txh240018@utdallas.edu",
"phone_number": "",
"office": {
"building": "",
@@ -61,17 +60,38 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "67d07ee0c972c18731e23bef"
+ "6972f54d6afb10b361a3e8cf"
]
},
{
- "_id": "67d07ee0c972c18731e23bf3",
- "first_name": "Tian",
- "last_name": "Hong",
+ "_id": "6972f54d6afb10b361a3e8d7",
+ "first_name": "Damian",
+ "last_name": "Enyaosah",
"titles": [
"Primary Instructor"
],
- "email": "txh240018@utdallas.edu",
+ "email": "dpe190000@utdallas.edu",
+ "phone_number": "",
+ "office": {
+ "building": "",
+ "room": "",
+ "map_uri": ""
+ },
+ "profile_uri": "",
+ "image_uri": "",
+ "office_hours": null,
+ "sections": [
+ "6972f54d6afb10b361a3e8d6"
+ ]
+ },
+ {
+ "_id": "6972f54d6afb10b361a3e8c8",
+ "first_name": "Naim Bugra",
+ "last_name": "Ozel",
+ "titles": [
+ "Primary Instructor (50%)"
+ ],
+ "email": "nbo150030@utdallas.edu",
"phone_number": "",
"office": {
"building": "",
@@ -82,7 +102,8 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "67d07ee0c972c18731e23bf2"
+ "6972f54d6afb10b361a3e8c7",
+ "6972f54d6afb10b361a3e8ca"
]
}
]
diff --git a/parser/testdata/sections.json b/parser/testdata/sections.json
index 92319de..da61c2f 100644
--- a/parser/testdata/sections.json
+++ b/parser/testdata/sections.json
@@ -1,8 +1,8 @@
[
{
- "_id": "67d07ee0c972c18731e23bef",
+ "_id": "6972f54d6afb10b361a3e8cc",
"section_number": "501",
- "course_reference": "67d07ee0c972c18731e23bee",
+ "course_reference": "6972f54d6afb10b361a3e8cb",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -10,7 +10,7 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "67d07ee0c972c18731e23bf0"
+ "6972f54d6afb10b361a3e8cd"
],
"teaching_assistants": [
{
@@ -45,13 +45,32 @@
"090"
],
"syllabus_uri": "https://dox.utdallas.edu/syl153033",
- "grade_distribution": [],
+ "grade_distribution": [
+ 0,
+ 13,
+ 23,
+ 6,
+ 4,
+ 7,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
"attributes": null
},
{
- "_id": "67d07ee0c972c18731e23bf2",
+ "_id": "6972f54d6afb10b361a3e8cf",
"section_number": "016",
- "course_reference": "67d07ee0c972c18731e23bf1",
+ "course_reference": "6972f54d6afb10b361a3e8ce",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -59,7 +78,7 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "67d07ee0c972c18731e23bf3"
+ "6972f54d6afb10b361a3e8d0"
],
"teaching_assistants": [],
"internal_class_number": "29611",
@@ -87,9 +106,9 @@
"attributes": null
},
{
- "_id": "67d07ee0c972c18731e23bf5",
+ "_id": "6972f54d6afb10b361a3e8d2",
"section_number": "201",
- "course_reference": "67d07ee0c972c18731e23bf4",
+ "course_reference": "6972f54d6afb10b361a3e8d1",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -123,9 +142,9 @@
"attributes": null
},
{
- "_id": "67d07ee0c972c18731e23bf7",
+ "_id": "6972f54d6afb10b361a3e8d4",
"section_number": "002",
- "course_reference": "67d07ee0c972c18731e23bf6",
+ "course_reference": "6972f54d6afb10b361a3e8d3",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -174,9 +193,70 @@
"attributes": null
},
{
- "_id": "67d07ee0c972c18731e23bea",
+ "_id": "6972f54d6afb10b361a3e8d6",
+ "section_number": "001",
+ "course_reference": "6972f54d6afb10b361a3e8d5",
+ "section_corequisites": null,
+ "academic_session": {
+ "name": "25S",
+ "start_date": "2025-01-21T00:00:00-06:00",
+ "end_date": "2025-05-16T00:00:00-05:00"
+ },
+ "professors": [
+ "6972f54d6afb10b361a3e8d7"
+ ],
+ "teaching_assistants": [],
+ "internal_class_number": "24043",
+ "instruction_mode": "Face-to-Face",
+ "meetings": [
+ {
+ "start_date": "2025-01-21T00:00:00-06:00",
+ "end_date": "2025-05-09T00:00:00-05:00",
+ "meeting_days": [
+ "Monday",
+ "Wednesday"
+ ],
+ "start_time": "10:00am",
+ "end_time": "11:15am",
+ "modality": "",
+ "location": {
+ "building": "FO",
+ "room": "1.202",
+ "map_uri": "https://locator.utdallas.edu/FO_1.202"
+ }
+ }
+ ],
+ "core_flags": [
+ "050",
+ "090"
+ ],
+ "syllabus_uri": "https://dox.utdallas.edu/syl154931",
+ "grade_distribution": [
+ 4,
+ 7,
+ 9,
+ 3,
+ 1,
+ 1,
+ 0,
+ 1,
+ 2,
+ 0,
+ 0,
+ 0,
+ 5,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "attributes": null
+ },
+ {
+ "_id": "6972f54d6afb10b361a3e8c7",
"section_number": "003",
- "course_reference": "67d07ee0c972c18731e23be9",
+ "course_reference": "6972f54d6afb10b361a3e8c6",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -184,8 +264,8 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "67d07ee0c972c18731e23beb",
- "67d07ee0c972c18731e23bec"
+ "6972f54d6afb10b361a3e8c8",
+ "6972f54d6afb10b361a3e8c9"
],
"teaching_assistants": [
{
@@ -223,13 +303,32 @@
],
"core_flags": [],
"syllabus_uri": "https://dox.utdallas.edu/syl152555",
- "grade_distribution": [],
+ "grade_distribution": [
+ 9,
+ 9,
+ 4,
+ 6,
+ 4,
+ 5,
+ 12,
+ 3,
+ 1,
+ 3,
+ 1,
+ 0,
+ 4,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
"attributes": null
},
{
- "_id": "67d07ee0c972c18731e23bed",
+ "_id": "6972f54d6afb10b361a3e8ca",
"section_number": "001",
- "course_reference": "67d07ee0c972c18731e23be9",
+ "course_reference": "6972f54d6afb10b361a3e8c6",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -237,8 +336,8 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "67d07ee0c972c18731e23bec",
- "67d07ee0c972c18731e23beb"
+ "6972f54d6afb10b361a3e8c9",
+ "6972f54d6afb10b361a3e8c8"
],
"teaching_assistants": [
{
@@ -276,7 +375,26 @@
],
"core_flags": [],
"syllabus_uri": "https://dox.utdallas.edu/syl152552",
- "grade_distribution": [],
+ "grade_distribution": [
+ 2,
+ 7,
+ 5,
+ 8,
+ 8,
+ 7,
+ 3,
+ 6,
+ 1,
+ 3,
+ 3,
+ 0,
+ 9,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
"attributes": null
}
]
diff --git a/parser/validator_test.go b/parser/validator_test.go
index c98a24b..d885cef 100644
--- a/parser/validator_test.go
+++ b/parser/validator_test.go
@@ -52,8 +52,15 @@ func init() {
panic(err)
}
- // The correct mapping
- indexMap = map[int]int{0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 4}
+ courseIndex := make(map[primitive.ObjectID]int)
+ for i, course := range testCourses {
+ courseIndex[course.Id] = i
+ }
+
+ indexMap = make(map[int]int, len(testSections))
+ for i, section := range testSections {
+ indexMap[i] = courseIndex[section.Course_reference]
+ }
}
// Test duplicate courses. Designed for fail cases
@@ -222,8 +229,8 @@ func TestSectionReferenceProfFail(t *testing.T) {
logOutput := logBuffer.String()
for _, msg := range []string{
- "Nonexistent professor reference found for section ID ObjectID(\"67d07ee0c972c18731e23bea\")!",
- "Referenced professor ID: ObjectID(\"67d07ee0c972c18731e23beb\")",
+ "Nonexistent professor reference found for section ID ObjectID(\"6972f54d6afb10b361a3e8c7\")!",
+ "Referenced professor ID: ObjectID(\"6972f54d6afb10b361a3e8c9\")",
} {
if !strings.Contains(logOutput, msg) {
t.Errorf("The function didn't log correct message. Expected \"%v\"", msg)
diff --git a/scrapers/coursebook.go b/scrapers/coursebook.go
index 4f6119c..539b6a8 100644
--- a/scrapers/coursebook.go
+++ b/scrapers/coursebook.go
@@ -25,7 +25,7 @@ import (
)
var (
- prefixRegex = regexp.MustCompile("cp_[a-z]{0,5}")
+ prefixRegex = regexp.MustCompile("^cp_[a-z]{1,5}$")
termRegex = regexp.MustCompile("[0-9]{1,2}[sfu]")
)
From 5ff2e0ea85305cbf18d063ab3847726bf400c548 Mon Sep 17 00:00:00 2001
From: Ian <94922205+SoggyRhino@users.noreply.github.com>
Date: Wed, 1 Apr 2026 18:35:14 -0500
Subject: [PATCH 5/5] fix whitespace error, fix logging
---
.gitignore | 1 -
parser/cometCalendarParser_test.go | 792 +++++++++++------------
parser/courseParser.go | 16 +-
parser/gradeLoader.go | 2 +-
parser/testdata/case_000/course.json | 6 +-
parser/testdata/case_000/professors.json | 8 +-
parser/testdata/case_000/section.json | 48 +-
parser/testdata/case_001/course.json | 6 +-
parser/testdata/case_001/professors.json | 8 +-
parser/testdata/case_001/section.json | 48 +-
parser/testdata/case_002/course.json | 6 +-
parser/testdata/case_002/professors.json | 4 +-
parser/testdata/case_002/section.json | 46 +-
parser/testdata/case_003/course.json | 6 +-
parser/testdata/case_003/professors.json | 4 +-
parser/testdata/case_003/section.json | 6 +-
parser/testdata/case_004/course.json | 6 +-
parser/testdata/case_004/section.json | 4 +-
parser/testdata/case_005/course.json | 6 +-
parser/testdata/case_005/section.json | 4 +-
parser/testdata/case_006/course.json | 6 +-
parser/testdata/case_006/professors.json | 42 +-
parser/testdata/case_006/section.json | 6 +-
parser/testdata/courses.json | 158 ++---
parser/testdata/professors.json | 24 +-
parser/testdata/sections.json | 314 ++++-----
parser/validator_test.go | 6 +-
27 files changed, 794 insertions(+), 789 deletions(-)
diff --git a/.gitignore b/.gitignore
index a6a1d1f..132e021 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,7 +43,6 @@ deploy_log.sh
.vscode/
.firebase/
/api-tools
-/qodana.yaml
# output data and logs
data/
diff --git a/parser/cometCalendarParser_test.go b/parser/cometCalendarParser_test.go
index 7f60cab..04e922c 100644
--- a/parser/cometCalendarParser_test.go
+++ b/parser/cometCalendarParser_test.go
@@ -1,396 +1,396 @@
-package parser
-
-import (
- "encoding/json"
- "os"
- "path/filepath"
- "slices"
- "testing"
- "time"
-
- "go.mongodb.org/mongo-driver/bson/primitive"
-
- "github.com/UTDNebula/nebula-api/api/schema"
- "github.com/google/go-cmp/cmp"
-)
-
-// helper function to read JSON files into Go structs for testing purposes
-func readJSONFile[T any](t *testing.T, path string) T {
- t.Helper()
-
- var result T
-
- data, err := os.ReadFile(path)
- if err != nil {
- t.Fatalf("failed to read file %q: %v", path, err)
- }
-
- if err := json.Unmarshal(data, &result); err != nil {
- t.Fatalf("failed to unmarshal JSON from file %q: %v", path, err)
- }
-
- return result
-}
-
-// helper function to write Go structs to JSON files for testing purposes
-func writeJSONFile(t *testing.T, path string, value any) {
- t.Helper()
-
- data, err := json.Marshal(value)
- if err != nil {
- t.Fatalf("failed to marshal value to JSON: %v", err)
- }
-
- if err := os.WriteFile(path, data, 0o777); err != nil {
- t.Fatalf("failed to write file %q: %v", path, err)
- }
-}
-
-// helper function to create a pointer to a string to match expected struct field
-func strPtr(s string) *string {
- return &s
-}
-
-// helper function to create valid schema.Event object for testing purposes
-func makeEvent(summary, location string, startTime time.Time) schema.Event {
- return schema.Event{
- Id: primitive.NewObjectID(),
- Summary: summary,
- Location: location,
- StartTime: startTime,
- EndTime: startTime.Add(time.Hour),
- }
-}
-
-// helper function to find a specific date entry in the parser result and fail if it is missing
-func findDate(
- t *testing.T,
- result []schema.MultiBuildingEvents[schema.Event],
- date string,
-) *schema.MultiBuildingEvents[schema.Event] {
- t.Helper()
-
- for i := range result {
- if result[i].Date == date {
- return &result[i]
- }
- }
-
- t.Fatalf("date %q not found in result", date)
- return nil
-}
-
-// helper function to find a specific building entry under a date and fail if it is missing
-func findBuilding(
- t *testing.T,
- dateEntry *schema.MultiBuildingEvents[schema.Event],
- building string,
-) *schema.SingleBuildingEvents[schema.Event] {
- t.Helper()
-
- for i := range dateEntry.Buildings {
- if dateEntry.Buildings[i].Building == building {
- return &dateEntry.Buildings[i]
- }
- }
-
- t.Fatalf("building %q not found under date %q", building, dateEntry.Date)
- return nil
-}
-
-// helper function to find a specific room entry under a building and fail if it is missing
-func findRoom(
- t *testing.T,
- buildingEntry *schema.SingleBuildingEvents[schema.Event],
- room string,
-) *schema.RoomEvents[schema.Event] {
- t.Helper()
-
- for i := range buildingEntry.Rooms {
- if buildingEntry.Rooms[i].Room == room {
- return &buildingEntry.Rooms[i]
- }
- }
-
- t.Fatalf("room %q not found under building %q", room, buildingEntry.Building)
- return nil
-}
-
-// helper function to create a sample set of map locations for testing purposes
-func testMapLocations() []schema.MapBuilding {
- return []schema.MapBuilding{
- {
- Name: strPtr("Engineering and Computer Science South (ECSS)"),
- Acronym: strPtr("ECSS"),
- },
- {
- Name: strPtr("Jonsson Performance Hall (JO)"),
- Acronym: strPtr("JO"),
- },
- }
-}
-
-// tests that getLocationAbbreviations correctly reads the mapLocations.json file and returns the expected building abbreviations and valid abbreviation list
-func TestGetLocationAbbreviations_Success(t *testing.T) {
- t.Parallel()
-
- inDir := t.TempDir()
- writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
-
- buildingAbbreviations, validAbbreviations, err := getLocationAbbreviations(inDir)
- if err != nil {
- t.Fatalf("getLocationAbbreviations returned an error: %v", err)
- }
-
- if got := buildingAbbreviations["Engineering and Computer Science South"]; got != "ECSS" {
- t.Fatalf("expected Engineering and Computer Science South -> ECSS, got %q", got)
- }
-
- if got := buildingAbbreviations["Jonsson Performance Hall"]; got != "JO" {
- t.Fatalf("expected Jonsson Performance Hall -> JO, got %q", got)
- }
-
- if !slices.Contains(validAbbreviations, "ECSS") {
- t.Fatalf("expected validAbbreviations to contain ECSS, got %v", validAbbreviations)
- }
-
- if !slices.Contains(validAbbreviations, "JO") {
- t.Fatalf("expected validAbbreviations to contain JO, got %v", validAbbreviations)
- }
-}
-
-// Tests that if a building has no acronym, an empty string is used as the abbreviation and is included in the validAbbreviations list
-func TestGetLocationAbbreviations_NoAcronym(t *testing.T) {
- t.Parallel()
-
- inDir := t.TempDir()
-
- locations := []schema.MapBuilding{
- {
- Name: strPtr("Unknown Building"),
- },
- }
-
- writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), locations)
-
- buildingAbbreviations, validAbbreviations, err := getLocationAbbreviations(inDir)
- if err != nil {
- t.Fatalf("getLocationAbbreviations returned an error: %v", err)
- }
-
- if got := buildingAbbreviations["Unknown Building"]; got != "" {
- t.Fatalf("expected empty-string abbreviation for building with no acronym, got %q", got)
- }
-
- if !slices.Contains(validAbbreviations, "") {
- t.Fatalf("expected validAbbreviations to contain empty string, got %v", validAbbreviations)
- }
-}
-
-// Tests that getLocationAbbreviations returns an error when mapLocations.json contains invalid JSON.
-func TestGetLocationAbbreviations_InvalidJSON(t *testing.T) {
- t.Parallel()
-
- inDir := t.TempDir()
-
- if err := os.WriteFile(filepath.Join(inDir, "mapLocations.json"), []byte("invalid json"), 0o777); err != nil {
- t.Fatalf("failed to write invalid json fixture: %v", err)
- }
-
- _, _, err := getLocationAbbreviations(inDir)
- if err == nil {
- t.Fatalf("expected error for invalid mapLocations.json, got nil")
- }
-}
-
-// Tests that ParseCometCalendar correctly processes a single event and stores it in the expected location in the output JSON structure.
-func TestParseCometCalendar_ParsesAbbreviationAndRoom(t *testing.T) {
- t.Parallel()
-
- inDir := t.TempDir()
- outDir := t.TempDir()
-
- writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
-
- start := time.Date(2026, 3, 14, 9, 0, 0, 0, time.UTC)
- events := []schema.Event{
- makeEvent("Test Event", "ECSS 2.415", start),
- }
- writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
-
- ParseCometCalendar(inDir, outDir)
-
- result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
- t,
- filepath.Join(outDir, "cometCalendar.json"),
- )
-
- dateEntry := findDate(t, result, "2026-03-14")
- buildingEntry := findBuilding(t, dateEntry, "ECSS")
- roomEntry := findRoom(t, buildingEntry, "2.415")
-
- if len(roomEntry.Events) != 1 {
- t.Fatalf("expected 1 event in ECSS/2.415 on 2026-03-14, got %d", len(roomEntry.Events))
- }
-
- if diff := cmp.Diff(events[0].Summary, roomEntry.Events[0].Summary); diff != "" {
- t.Fatalf("unexpected event stored in room (-want +got);\n%s", diff)
- }
-}
-
-// Tests that ParseCometCalendar correctly resolves full building names to their abbreviations.
-func TestParseCometCalendar_FallsBackToFullBuildingName(t *testing.T) {
- t.Parallel()
-
- inDir := t.TempDir()
- outDir := t.TempDir()
-
- writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
-
- start := time.Date(2026, 3, 14, 10, 0, 0, 0, time.UTC)
- events := []schema.Event{
- makeEvent("Full Building Name Event", "Engineering and Computer Science South 2.415", start),
- }
- writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
-
- ParseCometCalendar(inDir, outDir)
-
- result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
- t,
- filepath.Join(outDir, "cometCalendar.json"),
- )
-
- dateEntry := findDate(t, result, "2026-03-14")
- buildingEntry := findBuilding(t, dateEntry, "ECSS")
- roomEntry := findRoom(t, buildingEntry, "2.415")
-
- if len(roomEntry.Events) != 1 {
- t.Fatalf("expected 1 event in ECSS/2.415 on 2026-03-14, got %d", len(roomEntry.Events))
- }
-}
-
-// Test that if an event has a location that does not match any known building name or abbreviation, that it is categorized as "Other"
-func TestParseCometCalendar_UsesOtherForUnknownLocation(t *testing.T) {
- t.Parallel()
-
- inDir := t.TempDir()
- outDir := t.TempDir()
-
- writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
-
- start := time.Date(2026, 3, 14, 11, 0, 0, 0, time.UTC)
- events := []schema.Event{
- makeEvent("Unknown Location Event", "Off Campus Location", start),
- }
- writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
-
- ParseCometCalendar(inDir, outDir)
-
- result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
- t,
- filepath.Join(outDir, "cometCalendar.json"),
- )
-
- dateEntry := findDate(t, result, "2026-03-14")
- buildingEntry := findBuilding(t, dateEntry, "Other")
- roomEntry := findRoom(t, buildingEntry, "Other")
-
- if len(roomEntry.Events) != 1 {
- t.Fatalf("expected 1 event in Other/Other on 2026-03-14, got %d", len(roomEntry.Events))
- }
-}
-
-// Tests that if multiple events occur in the same building/room on the same day, they are grouped together in the JSON output structure
-func TestParseCometCalendar_GroupsEventsByDateBuildingRoom(t *testing.T) {
- t.Parallel()
-
- inDir := t.TempDir()
- outDir := t.TempDir()
-
- writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
-
- start1 := time.Date(2026, 3, 14, 9, 0, 0, 0, time.UTC)
- start2 := time.Date(2026, 3, 14, 13, 0, 0, 0, time.UTC)
- events := []schema.Event{
- makeEvent("First Grouped Event", "ECSS 2.415", start1),
- makeEvent("Second Grouped Event", "ECSS 2.415", start2),
- }
- writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
-
- ParseCometCalendar(inDir, outDir)
-
- result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
- t,
- filepath.Join(outDir, "cometCalendar.json"),
- )
-
- dateEntry := findDate(t, result, "2026-03-14")
- buildingEntry := findBuilding(t, dateEntry, "ECSS")
- roomEntry := findRoom(t, buildingEntry, "2.415")
-
- if len(roomEntry.Events) != 2 {
- t.Fatalf("expected 2 events in ECSS/2.415 on 2026-03-14, got %d", len(roomEntry.Events))
- }
-}
-
-// Tests that ParseCometCalendar uses the comma-separated fallback to extract the room when the building is valid and no room was otherwise found.
-func TestParseCometCalendar_UsesCommaSeparatedFallbackRoom(t *testing.T) {
- t.Parallel()
-
- inDir := t.TempDir()
- outDir := t.TempDir()
-
- writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
-
- start := time.Date(2026, 3, 14, 12, 0, 0, 0, time.UTC)
- events := []schema.Event{
- makeEvent("Conference Room Event", "ECSS, Conference Room", start),
- }
- writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
-
- ParseCometCalendar(inDir, outDir)
-
- result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
- t,
- filepath.Join(outDir, "cometCalendar.json"),
- )
-
- dateEntry := findDate(t, result, "2026-03-14")
- buildingEntry := findBuilding(t, dateEntry, "ECSS")
- roomEntry := findRoom(t, buildingEntry, "Conference Room")
-
- if len(roomEntry.Events) != 1 {
- t.Fatalf("expected 1 event in the ECSS/Conference Room, got %d", len(roomEntry.Events))
- }
-}
-
-// Tests that if an event has an empty location, it is categorized as "Other"
-func TestParseCometCalendar_UsesOtherForEmptyLocation(t *testing.T) {
- t.Parallel()
-
- inDir := t.TempDir()
- outDir := t.TempDir()
-
- writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
-
- start := time.Date(2026, 3, 14, 14, 0, 0, 0, time.UTC)
- events := []schema.Event{
- makeEvent("Empty Location Event", "", start),
- }
- writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
-
- ParseCometCalendar(inDir, outDir)
-
- result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
- t,
- filepath.Join(outDir, "cometCalendar.json"),
- )
-
- dateEntry := findDate(t, result, "2026-03-14")
- buildingEntry := findBuilding(t, dateEntry, "Other")
- roomEntry := findRoom(t, buildingEntry, "Other")
-
- if len(roomEntry.Events) != 1 {
- t.Fatalf("expected 1 event in Other/Other, got %d", len(roomEntry.Events))
- }
-}
+package parser
+
+import (
+ "encoding/json"
+ "os"
+ "path/filepath"
+ "slices"
+ "testing"
+ "time"
+
+ "go.mongodb.org/mongo-driver/bson/primitive"
+
+ "github.com/UTDNebula/nebula-api/api/schema"
+ "github.com/google/go-cmp/cmp"
+)
+
+// helper function to read JSON files into Go structs for testing purposes
+func readJSONFile[T any](t *testing.T, path string) T {
+ t.Helper()
+
+ var result T
+
+ data, err := os.ReadFile(path)
+ if err != nil {
+ t.Fatalf("failed to read file %q: %v", path, err)
+ }
+
+ if err := json.Unmarshal(data, &result); err != nil {
+ t.Fatalf("failed to unmarshal JSON from file %q: %v", path, err)
+ }
+
+ return result
+}
+
+// helper function to write Go structs to JSON files for testing purposes
+func writeJSONFile(t *testing.T, path string, value any) {
+ t.Helper()
+
+ data, err := json.Marshal(value)
+ if err != nil {
+ t.Fatalf("failed to marshal value to JSON: %v", err)
+ }
+
+ if err := os.WriteFile(path, data, 0o777); err != nil {
+ t.Fatalf("failed to write file %q: %v", path, err)
+ }
+}
+
+// helper function to create a pointer to a string to match expected struct field
+func strPtr(s string) *string {
+ return &s
+}
+
+// helper function to create valid schema.Event object for testing purposes
+func makeEvent(summary, location string, startTime time.Time) schema.Event {
+ return schema.Event{
+ Id: primitive.NewObjectID(),
+ Summary: summary,
+ Location: location,
+ StartTime: startTime,
+ EndTime: startTime.Add(time.Hour),
+ }
+}
+
+// helper function to find a specific date entry in the parser result and fail if it is missing
+func findDate(
+ t *testing.T,
+ result []schema.MultiBuildingEvents[schema.Event],
+ date string,
+) *schema.MultiBuildingEvents[schema.Event] {
+ t.Helper()
+
+ for i := range result {
+ if result[i].Date == date {
+ return &result[i]
+ }
+ }
+
+ t.Fatalf("date %q not found in result", date)
+ return nil
+}
+
+// helper function to find a specific building entry under a date and fail if it is missing
+func findBuilding(
+ t *testing.T,
+ dateEntry *schema.MultiBuildingEvents[schema.Event],
+ building string,
+) *schema.SingleBuildingEvents[schema.Event] {
+ t.Helper()
+
+ for i := range dateEntry.Buildings {
+ if dateEntry.Buildings[i].Building == building {
+ return &dateEntry.Buildings[i]
+ }
+ }
+
+ t.Fatalf("building %q not found under date %q", building, dateEntry.Date)
+ return nil
+}
+
+// helper function to find a specific room entry under a building and fail if it is missing
+func findRoom(
+ t *testing.T,
+ buildingEntry *schema.SingleBuildingEvents[schema.Event],
+ room string,
+) *schema.RoomEvents[schema.Event] {
+ t.Helper()
+
+ for i := range buildingEntry.Rooms {
+ if buildingEntry.Rooms[i].Room == room {
+ return &buildingEntry.Rooms[i]
+ }
+ }
+
+ t.Fatalf("room %q not found under building %q", room, buildingEntry.Building)
+ return nil
+}
+
+// helper function to create a sample set of map locations for testing purposes
+func testMapLocations() []schema.MapBuilding {
+ return []schema.MapBuilding{
+ {
+ Name: strPtr("Engineering and Computer Science South (ECSS)"),
+ Acronym: strPtr("ECSS"),
+ },
+ {
+ Name: strPtr("Jonsson Performance Hall (JO)"),
+ Acronym: strPtr("JO"),
+ },
+ }
+}
+
+// tests that getLocationAbbreviations correctly reads the mapLocations.json file and returns the expected building abbreviations and valid abbreviation list
+func TestGetLocationAbbreviations_Success(t *testing.T) {
+ t.Parallel()
+
+ inDir := t.TempDir()
+ writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
+
+ buildingAbbreviations, validAbbreviations, err := getLocationAbbreviations(inDir)
+ if err != nil {
+ t.Fatalf("getLocationAbbreviations returned an error: %v", err)
+ }
+
+ if got := buildingAbbreviations["Engineering and Computer Science South"]; got != "ECSS" {
+ t.Fatalf("expected Engineering and Computer Science South -> ECSS, got %q", got)
+ }
+
+ if got := buildingAbbreviations["Jonsson Performance Hall"]; got != "JO" {
+ t.Fatalf("expected Jonsson Performance Hall -> JO, got %q", got)
+ }
+
+ if !slices.Contains(validAbbreviations, "ECSS") {
+ t.Fatalf("expected validAbbreviations to contain ECSS, got %v", validAbbreviations)
+ }
+
+ if !slices.Contains(validAbbreviations, "JO") {
+ t.Fatalf("expected validAbbreviations to contain JO, got %v", validAbbreviations)
+ }
+}
+
+// Tests that if a building has no acronym, an empty string is used as the abbreviation and is included in the validAbbreviations list
+func TestGetLocationAbbreviations_NoAcronym(t *testing.T) {
+ t.Parallel()
+
+ inDir := t.TempDir()
+
+ locations := []schema.MapBuilding{
+ {
+ Name: strPtr("Unknown Building"),
+ },
+ }
+
+ writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), locations)
+
+ buildingAbbreviations, validAbbreviations, err := getLocationAbbreviations(inDir)
+ if err != nil {
+ t.Fatalf("getLocationAbbreviations returned an error: %v", err)
+ }
+
+ if got := buildingAbbreviations["Unknown Building"]; got != "" {
+ t.Fatalf("expected empty-string abbreviation for building with no acronym, got %q", got)
+ }
+
+ if !slices.Contains(validAbbreviations, "") {
+ t.Fatalf("expected validAbbreviations to contain empty string, got %v", validAbbreviations)
+ }
+}
+
+// Tests that getLocationAbbreviations returns an error when mapLocations.json contains invalid JSON.
+func TestGetLocationAbbreviations_InvalidJSON(t *testing.T) {
+ t.Parallel()
+
+ inDir := t.TempDir()
+
+ if err := os.WriteFile(filepath.Join(inDir, "mapLocations.json"), []byte("invalid json"), 0o777); err != nil {
+ t.Fatalf("failed to write invalid json fixture: %v", err)
+ }
+
+ _, _, err := getLocationAbbreviations(inDir)
+ if err == nil {
+ t.Fatalf("expected error for invalid mapLocations.json, got nil")
+ }
+}
+
+// Tests that ParseCometCalendar correctly processes a single event and stores it in the expected location in the output JSON structure.
+func TestParseCometCalendar_ParsesAbbreviationAndRoom(t *testing.T) {
+ t.Parallel()
+
+ inDir := t.TempDir()
+ outDir := t.TempDir()
+
+ writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
+
+ start := time.Date(2026, 3, 14, 9, 0, 0, 0, time.UTC)
+ events := []schema.Event{
+ makeEvent("Test Event", "ECSS 2.415", start),
+ }
+ writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
+
+ ParseCometCalendar(inDir, outDir)
+
+ result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
+ t,
+ filepath.Join(outDir, "cometCalendar.json"),
+ )
+
+ dateEntry := findDate(t, result, "2026-03-14")
+ buildingEntry := findBuilding(t, dateEntry, "ECSS")
+ roomEntry := findRoom(t, buildingEntry, "2.415")
+
+ if len(roomEntry.Events) != 1 {
+ t.Fatalf("expected 1 event in ECSS/2.415 on 2026-03-14, got %d", len(roomEntry.Events))
+ }
+
+ if diff := cmp.Diff(events[0].Summary, roomEntry.Events[0].Summary); diff != "" {
+ t.Fatalf("unexpected event stored in room (-want +got);\n%s", diff)
+ }
+}
+
+// Tests that ParseCometCalendar correctly resolves full building names to their abbreviations.
+func TestParseCometCalendar_FallsBackToFullBuildingName(t *testing.T) {
+ t.Parallel()
+
+ inDir := t.TempDir()
+ outDir := t.TempDir()
+
+ writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
+
+ start := time.Date(2026, 3, 14, 10, 0, 0, 0, time.UTC)
+ events := []schema.Event{
+ makeEvent("Full Building Name Event", "Engineering and Computer Science South 2.415", start),
+ }
+ writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
+
+ ParseCometCalendar(inDir, outDir)
+
+ result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
+ t,
+ filepath.Join(outDir, "cometCalendar.json"),
+ )
+
+ dateEntry := findDate(t, result, "2026-03-14")
+ buildingEntry := findBuilding(t, dateEntry, "ECSS")
+ roomEntry := findRoom(t, buildingEntry, "2.415")
+
+ if len(roomEntry.Events) != 1 {
+ t.Fatalf("expected 1 event in ECSS/2.415 on 2026-03-14, got %d", len(roomEntry.Events))
+ }
+}
+
+// Test that if an event has a location that does not match any known building name or abbreviation, that it is categorized as "Other"
+func TestParseCometCalendar_UsesOtherForUnknownLocation(t *testing.T) {
+ t.Parallel()
+
+ inDir := t.TempDir()
+ outDir := t.TempDir()
+
+ writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
+
+ start := time.Date(2026, 3, 14, 11, 0, 0, 0, time.UTC)
+ events := []schema.Event{
+ makeEvent("Unknown Location Event", "Off Campus Location", start),
+ }
+ writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
+
+ ParseCometCalendar(inDir, outDir)
+
+ result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
+ t,
+ filepath.Join(outDir, "cometCalendar.json"),
+ )
+
+ dateEntry := findDate(t, result, "2026-03-14")
+ buildingEntry := findBuilding(t, dateEntry, "Other")
+ roomEntry := findRoom(t, buildingEntry, "Other")
+
+ if len(roomEntry.Events) != 1 {
+ t.Fatalf("expected 1 event in Other/Other on 2026-03-14, got %d", len(roomEntry.Events))
+ }
+}
+
+// Tests that if multiple events occur in the same building/room on the same day, they are grouped together in the JSON output structure
+func TestParseCometCalendar_GroupsEventsByDateBuildingRoom(t *testing.T) {
+ t.Parallel()
+
+ inDir := t.TempDir()
+ outDir := t.TempDir()
+
+ writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
+
+ start1 := time.Date(2026, 3, 14, 9, 0, 0, 0, time.UTC)
+ start2 := time.Date(2026, 3, 14, 13, 0, 0, 0, time.UTC)
+ events := []schema.Event{
+ makeEvent("First Grouped Event", "ECSS 2.415", start1),
+ makeEvent("Second Grouped Event", "ECSS 2.415", start2),
+ }
+ writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
+
+ ParseCometCalendar(inDir, outDir)
+
+ result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
+ t,
+ filepath.Join(outDir, "cometCalendar.json"),
+ )
+
+ dateEntry := findDate(t, result, "2026-03-14")
+ buildingEntry := findBuilding(t, dateEntry, "ECSS")
+ roomEntry := findRoom(t, buildingEntry, "2.415")
+
+ if len(roomEntry.Events) != 2 {
+ t.Fatalf("expected 2 events in ECSS/2.415 on 2026-03-14, got %d", len(roomEntry.Events))
+ }
+}
+
+// Tests that ParseCometCalendar uses the comma-separated fallback to extract the room when the building is valid and no room was otherwise found.
+func TestParseCometCalendar_UsesCommaSeparatedFallbackRoom(t *testing.T) {
+ t.Parallel()
+
+ inDir := t.TempDir()
+ outDir := t.TempDir()
+
+ writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
+
+ start := time.Date(2026, 3, 14, 12, 0, 0, 0, time.UTC)
+ events := []schema.Event{
+ makeEvent("Conference Room Event", "ECSS, Conference Room", start),
+ }
+ writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
+
+ ParseCometCalendar(inDir, outDir)
+
+ result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
+ t,
+ filepath.Join(outDir, "cometCalendar.json"),
+ )
+
+ dateEntry := findDate(t, result, "2026-03-14")
+ buildingEntry := findBuilding(t, dateEntry, "ECSS")
+ roomEntry := findRoom(t, buildingEntry, "Conference Room")
+
+ if len(roomEntry.Events) != 1 {
+ t.Fatalf("expected 1 event in the ECSS/Conference Room, got %d", len(roomEntry.Events))
+ }
+}
+
+// Tests that if an event has an empty location, it is categorized as "Other"
+func TestParseCometCalendar_UsesOtherForEmptyLocation(t *testing.T) {
+ t.Parallel()
+
+ inDir := t.TempDir()
+ outDir := t.TempDir()
+
+ writeJSONFile(t, filepath.Join(inDir, "mapLocations.json"), testMapLocations())
+
+ start := time.Date(2026, 3, 14, 14, 0, 0, 0, time.UTC)
+ events := []schema.Event{
+ makeEvent("Empty Location Event", "", start),
+ }
+ writeJSONFile(t, filepath.Join(inDir, "cometCalendarScraped.json"), events)
+
+ ParseCometCalendar(inDir, outDir)
+
+ result := readJSONFile[[]schema.MultiBuildingEvents[schema.Event]](
+ t,
+ filepath.Join(outDir, "cometCalendar.json"),
+ )
+
+ dateEntry := findDate(t, result, "2026-03-14")
+ buildingEntry := findBuilding(t, dateEntry, "Other")
+ roomEntry := findRoom(t, buildingEntry, "Other")
+
+ if len(roomEntry.Events) != 1 {
+ t.Fatalf("expected 1 event in Other/Other, got %d", len(roomEntry.Events))
+ }
+}
diff --git a/parser/courseParser.go b/parser/courseParser.go
index 7d59baf..5bacd98 100644
--- a/parser/courseParser.go
+++ b/parser/courseParser.go
@@ -19,6 +19,9 @@ var (
// contactRegexp matches the contact hours and offering frequency from the course description
// (e.g. "(12-34) SUS")
contactRegexp = regexp.MustCompile(`\(([0-9]+)-([0-9]+)\)\s+([SUFY]+)`)
+
+ // descriptionWhitespaceRegexp matches one or more whitespace characters (including newlines)
+ descriptionWhitespaceRegexp = regexp.MustCompile(`\s+`)
)
// parseCourse returns a pointer to the course specified by the
@@ -50,15 +53,18 @@ func parseCourse(internalCourseNumber string, session schema.AcademicSession, ro
// This function does not modify any global state.
// Returns a pointer to the newly created schema.Course object.
func getCourse(internalCourseNumber string, session schema.AcademicSession, rowInfo map[string]*goquery.Selection, classInfo map[string]string) *schema.Course {
- CoursePrefix, CourseNumber := getPrefixAndNumber(classInfo)
+ coursePrefix, courseNumber := getPrefixAndNumber(classInfo)
+
+ description := descriptionWhitespaceRegexp.ReplaceAllString(rowInfo["Description:"].Text(), " ")
+ school := descriptionWhitespaceRegexp.ReplaceAllString(rowInfo["College:"].Text(), " ")
course := schema.Course{
Id: primitive.NewObjectID(),
- Course_number: CourseNumber,
- Subject_prefix: CoursePrefix,
+ Course_number: courseNumber,
+ Subject_prefix: coursePrefix,
Title: utils.TrimWhitespace(rowInfo["Course Title:"].Text()),
- Description: utils.TrimWhitespace(rowInfo["Description:"].Text()),
- School: utils.TrimWhitespace(rowInfo["College:"].Text()),
+ Description: utils.TrimWhitespace(description),
+ School: utils.TrimWhitespace(school),
Credit_hours: classInfo["Semester Credit Hours:"],
Class_level: classInfo["Class Level:"],
Activity_type: classInfo["Activity Type:"],
diff --git a/parser/gradeLoader.go b/parser/gradeLoader.go
index bde878a..ba51876 100644
--- a/parser/gradeLoader.go
+++ b/parser/gradeLoader.go
@@ -82,7 +82,7 @@ func csvToMap(filename string) (map[string][]int, error) {
for _, name := range optionalColumns {
if _, ok := indexMap[name]; !ok {
- fmt.Fprintf(logFile, "could not find %s column\n", name)
+ utils.VPrintf("could not find %s column in %s", name, filename)
}
}
diff --git a/parser/testdata/case_000/course.json b/parser/testdata/case_000/course.json
index e050494..3f26745 100644
--- a/parser/testdata/case_000/course.json
+++ b/parser/testdata/case_000/course.json
@@ -1,9 +1,9 @@
{
- "_id": "6972f54d6afb10b361a3e8b1",
+ "_id": "69cda9299bb0b8cd4cf4c71c",
"subject_prefix": "ACCT",
"course_number": "2301",
"title": "Introductory Financial Accounting",
- "description": "ACCT 2301 - Introductory Financial Accounting (3 semester credit hours) An introduction to financial reporting designed to create an awareness of the accounting concepts and principles for preparing the three basic financial statements: the income statement, balance sheet, and statement of cash flows. A minimum grade of C is required to take upper-division ACCT courses. (3-0) S",
+ "description": "ACCT 2301 - Introductory Financial Accounting (3 semester credit hours) An introduction to financial reporting designed to create an awareness of the accounting concepts and principles for preparing the three basic financial statements: the income statement, balance sheet, and statement of cash flows. A minimum grade of C is required to take upper-division ACCT courses. (3-0) S",
"enrollment_reqs": "ACCT 2301 Repeat Restriction",
"school": "Naveen Jindal School of Management",
"credit_hours": "3",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8b2"
+ "69cda9299bb0b8cd4cf4c71d"
],
"lecture_contact_hours": "3",
"laboratory_contact_hours": "0",
diff --git a/parser/testdata/case_000/professors.json b/parser/testdata/case_000/professors.json
index 57aec29..b3436be 100644
--- a/parser/testdata/case_000/professors.json
+++ b/parser/testdata/case_000/professors.json
@@ -1,6 +1,6 @@
[
{
- "_id": "6972f54d6afb10b361a3e8b3",
+ "_id": "69cda9299bb0b8cd4cf4c71e",
"first_name": "Naim Bugra",
"last_name": "Ozel",
"titles": [
@@ -17,11 +17,11 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8b2"
+ "69cda9299bb0b8cd4cf4c71d"
]
},
{
- "_id": "6972f54d6afb10b361a3e8b4",
+ "_id": "69cda9299bb0b8cd4cf4c71f",
"first_name": "Jieying",
"last_name": "Zhang",
"titles": [
@@ -38,7 +38,7 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8b2"
+ "69cda9299bb0b8cd4cf4c71d"
]
}
]
diff --git a/parser/testdata/case_000/section.json b/parser/testdata/case_000/section.json
index b2faa8f..adf994c 100644
--- a/parser/testdata/case_000/section.json
+++ b/parser/testdata/case_000/section.json
@@ -1,7 +1,7 @@
{
- "_id": "6972f54d6afb10b361a3e8b2",
+ "_id": "69cda9299bb0b8cd4cf4c71d",
"section_number": "003",
- "course_reference": "6972f54d6afb10b361a3e8b1",
+ "course_reference": "69cda9299bb0b8cd4cf4c71c",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -9,8 +9,8 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "6972f54d6afb10b361a3e8b3",
- "6972f54d6afb10b361a3e8b4"
+ "69cda9299bb0b8cd4cf4c71e",
+ "69cda9299bb0b8cd4cf4c71f"
],
"teaching_assistants": [
{
@@ -48,25 +48,25 @@
],
"core_flags": [],
"syllabus_uri": "https://dox.utdallas.edu/syl152555",
- "grade_distribution": [
- 9,
- 9,
- 4,
- 6,
- 4,
- 5,
- 12,
- 3,
- 1,
- 3,
- 1,
- 0,
- 4,
- 3,
- 0,
- 0,
- 0,
- 0
- ],
+ "grade_distribution": [
+ 9,
+ 9,
+ 4,
+ 6,
+ 4,
+ 5,
+ 12,
+ 3,
+ 1,
+ 3,
+ 1,
+ 0,
+ 4,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
"attributes": null
}
diff --git a/parser/testdata/case_001/course.json b/parser/testdata/case_001/course.json
index 4a015f2..fd0a985 100644
--- a/parser/testdata/case_001/course.json
+++ b/parser/testdata/case_001/course.json
@@ -1,9 +1,9 @@
{
- "_id": "6972f54d6afb10b361a3e8b5",
+ "_id": "69cda9299bb0b8cd4cf4c720",
"subject_prefix": "ACCT",
"course_number": "2301",
"title": "Introductory Financial Accounting",
- "description": "ACCT 2301 - Introductory Financial Accounting (3 semester credit hours) An introduction to financial reporting designed to create an awareness of the accounting concepts and principles for preparing the three basic financial statements: the income statement, balance sheet, and statement of cash flows. A minimum grade of C is required to take upper-division ACCT courses. (3-0) S",
+ "description": "ACCT 2301 - Introductory Financial Accounting (3 semester credit hours) An introduction to financial reporting designed to create an awareness of the accounting concepts and principles for preparing the three basic financial statements: the income statement, balance sheet, and statement of cash flows. A minimum grade of C is required to take upper-division ACCT courses. (3-0) S",
"enrollment_reqs": "ACCT 2301 Repeat Restriction",
"school": "Naveen Jindal School of Management",
"credit_hours": "3",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8b6"
+ "69cda9299bb0b8cd4cf4c721"
],
"lecture_contact_hours": "3",
"laboratory_contact_hours": "0",
diff --git a/parser/testdata/case_001/professors.json b/parser/testdata/case_001/professors.json
index 72111e2..1231428 100644
--- a/parser/testdata/case_001/professors.json
+++ b/parser/testdata/case_001/professors.json
@@ -1,6 +1,6 @@
[
{
- "_id": "6972f54d6afb10b361a3e8b7",
+ "_id": "69cda9299bb0b8cd4cf4c722",
"first_name": "Jieying",
"last_name": "Zhang",
"titles": [
@@ -17,11 +17,11 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8b6"
+ "69cda9299bb0b8cd4cf4c721"
]
},
{
- "_id": "6972f54d6afb10b361a3e8b8",
+ "_id": "69cda9299bb0b8cd4cf4c723",
"first_name": "Naim Bugra",
"last_name": "Ozel",
"titles": [
@@ -38,7 +38,7 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8b6"
+ "69cda9299bb0b8cd4cf4c721"
]
}
]
diff --git a/parser/testdata/case_001/section.json b/parser/testdata/case_001/section.json
index 4b10fcf..a5039fd 100644
--- a/parser/testdata/case_001/section.json
+++ b/parser/testdata/case_001/section.json
@@ -1,7 +1,7 @@
{
- "_id": "6972f54d6afb10b361a3e8b6",
+ "_id": "69cda9299bb0b8cd4cf4c721",
"section_number": "001",
- "course_reference": "6972f54d6afb10b361a3e8b5",
+ "course_reference": "69cda9299bb0b8cd4cf4c720",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -9,8 +9,8 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "6972f54d6afb10b361a3e8b7",
- "6972f54d6afb10b361a3e8b8"
+ "69cda9299bb0b8cd4cf4c722",
+ "69cda9299bb0b8cd4cf4c723"
],
"teaching_assistants": [
{
@@ -48,25 +48,25 @@
],
"core_flags": [],
"syllabus_uri": "https://dox.utdallas.edu/syl152552",
- "grade_distribution": [
- 2,
- 7,
- 5,
- 8,
- 8,
- 7,
- 3,
- 6,
- 1,
- 3,
- 3,
- 0,
- 9,
- 1,
- 0,
- 0,
- 0,
- 0
- ],
+ "grade_distribution": [
+ 2,
+ 7,
+ 5,
+ 8,
+ 8,
+ 7,
+ 3,
+ 6,
+ 1,
+ 3,
+ 3,
+ 0,
+ 9,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
"attributes": null
}
diff --git a/parser/testdata/case_002/course.json b/parser/testdata/case_002/course.json
index 1db1746..8693456 100644
--- a/parser/testdata/case_002/course.json
+++ b/parser/testdata/case_002/course.json
@@ -1,9 +1,9 @@
{
- "_id": "6972f54d6afb10b361a3e8b9",
+ "_id": "69cda9299bb0b8cd4cf4c724",
"subject_prefix": "BA",
"course_number": "1320",
"title": "Business in a Global World",
- "description": "BA 1320 - Business in a Global World (3 semester credit hours) This course provides a primer on the impacts of globalization on business. We equip students with the basic facts of globalization and examine the business underpinnings and the institutions that shape globalization. We discuss major trends and the future of international management. The aim is an ability to think strategically and critically about global business issues. (3-0) S",
+ "description": "BA 1320 - Business in a Global World (3 semester credit hours) This course provides a primer on the impacts of globalization on business. We equip students with the basic facts of globalization and examine the business underpinnings and the institutions that shape globalization. We discuss major trends and the future of international management. The aim is an ability to think strategically and critically about global business issues. (3-0) S",
"enrollment_reqs": "BA 1320 Repeat Restriction",
"school": "Naveen Jindal School of Management",
"credit_hours": "3",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8ba"
+ "69cda9299bb0b8cd4cf4c725"
],
"lecture_contact_hours": "3",
"laboratory_contact_hours": "0",
diff --git a/parser/testdata/case_002/professors.json b/parser/testdata/case_002/professors.json
index 1f17b80..0316d96 100644
--- a/parser/testdata/case_002/professors.json
+++ b/parser/testdata/case_002/professors.json
@@ -1,6 +1,6 @@
[
{
- "_id": "6972f54d6afb10b361a3e8bb",
+ "_id": "69cda9299bb0b8cd4cf4c726",
"first_name": "Peter",
"last_name": "Lewin",
"titles": [
@@ -17,7 +17,7 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8ba"
+ "69cda9299bb0b8cd4cf4c725"
]
}
]
diff --git a/parser/testdata/case_002/section.json b/parser/testdata/case_002/section.json
index 4ca35d9..e28a111 100644
--- a/parser/testdata/case_002/section.json
+++ b/parser/testdata/case_002/section.json
@@ -1,7 +1,7 @@
{
- "_id": "6972f54d6afb10b361a3e8ba",
+ "_id": "69cda9299bb0b8cd4cf4c725",
"section_number": "501",
- "course_reference": "6972f54d6afb10b361a3e8b9",
+ "course_reference": "69cda9299bb0b8cd4cf4c724",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -9,7 +9,7 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "6972f54d6afb10b361a3e8bb"
+ "69cda9299bb0b8cd4cf4c726"
],
"teaching_assistants": [
{
@@ -44,25 +44,25 @@
"090"
],
"syllabus_uri": "https://dox.utdallas.edu/syl153033",
- "grade_distribution": [
- 0,
- 13,
- 23,
- 6,
- 4,
- 7,
- 1,
- 0,
- 0,
- 0,
- 0,
- 0,
- 1,
- 1,
- 0,
- 0,
- 0,
- 0
- ],
+ "grade_distribution": [
+ 0,
+ 13,
+ 23,
+ 6,
+ 4,
+ 7,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
"attributes": null
}
diff --git a/parser/testdata/case_003/course.json b/parser/testdata/case_003/course.json
index 0111554..308850d 100644
--- a/parser/testdata/case_003/course.json
+++ b/parser/testdata/case_003/course.json
@@ -1,9 +1,9 @@
{
- "_id": "6972f54d6afb10b361a3e8bc",
+ "_id": "69cda9299bb0b8cd4cf4c727",
"subject_prefix": "BIOL",
"course_number": "6111",
"title": "Graduate Research Presentation",
- "description": "BIOL 6111 - Graduate Research Presentation (1 semester credit hour) This course will train graduate students (MS and PhD) in hypothesis building and testing, designing, and conducting experiments, and presenting scientific findings in an efficient and clear manner. During the class, graduate students will discuss and present their graduate research work-in-progress. Significant time outside of class will also be required to analyze data, assemble, and practice presentations. May be repeated for credit as topics vary (2 semester credit hours maximum). Department consent required. (1-0) S",
+ "description": "BIOL 6111 - Graduate Research Presentation (1 semester credit hour) This course will train graduate students (MS and PhD) in hypothesis building and testing, designing, and conducting experiments, and presenting scientific findings in an efficient and clear manner. During the class, graduate students will discuss and present their graduate research work-in-progress. Significant time outside of class will also be required to analyze data, assemble, and practice presentations. May be repeated for credit as topics vary (2 semester credit hours maximum). Department consent required. (1-0) S",
"enrollment_reqs": "",
"school": "School of Natural Sciences and Mathematics",
"credit_hours": "1",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8bd"
+ "69cda9299bb0b8cd4cf4c728"
],
"lecture_contact_hours": "1",
"laboratory_contact_hours": "0",
diff --git a/parser/testdata/case_003/professors.json b/parser/testdata/case_003/professors.json
index e921077..9913649 100644
--- a/parser/testdata/case_003/professors.json
+++ b/parser/testdata/case_003/professors.json
@@ -1,6 +1,6 @@
[
{
- "_id": "6972f54d6afb10b361a3e8be",
+ "_id": "69cda9299bb0b8cd4cf4c729",
"first_name": "Tian",
"last_name": "Hong",
"titles": [
@@ -17,7 +17,7 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8bd"
+ "69cda9299bb0b8cd4cf4c728"
]
}
]
diff --git a/parser/testdata/case_003/section.json b/parser/testdata/case_003/section.json
index d598a10..93541b3 100644
--- a/parser/testdata/case_003/section.json
+++ b/parser/testdata/case_003/section.json
@@ -1,7 +1,7 @@
{
- "_id": "6972f54d6afb10b361a3e8bd",
+ "_id": "69cda9299bb0b8cd4cf4c728",
"section_number": "016",
- "course_reference": "6972f54d6afb10b361a3e8bc",
+ "course_reference": "69cda9299bb0b8cd4cf4c727",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -9,7 +9,7 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "6972f54d6afb10b361a3e8be"
+ "69cda9299bb0b8cd4cf4c729"
],
"teaching_assistants": [],
"internal_class_number": "29611",
diff --git a/parser/testdata/case_004/course.json b/parser/testdata/case_004/course.json
index b253341..b5431c7 100644
--- a/parser/testdata/case_004/course.json
+++ b/parser/testdata/case_004/course.json
@@ -1,9 +1,9 @@
{
- "_id": "6972f54d6afb10b361a3e8bf",
+ "_id": "69cda9299bb0b8cd4cf4c72a",
"subject_prefix": "AERO",
"course_number": "3320",
"title": "- Recitation",
- "description": "- ()",
+ "description": "- ()",
"enrollment_reqs": "",
"school": "Undergraduate Studies",
"credit_hours": "Non-Enroll",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8c0"
+ "69cda9299bb0b8cd4cf4c72b"
],
"lecture_contact_hours": "",
"laboratory_contact_hours": "",
diff --git a/parser/testdata/case_004/section.json b/parser/testdata/case_004/section.json
index 0608366..bdc3217 100644
--- a/parser/testdata/case_004/section.json
+++ b/parser/testdata/case_004/section.json
@@ -1,7 +1,7 @@
{
- "_id": "6972f54d6afb10b361a3e8c0",
+ "_id": "69cda9299bb0b8cd4cf4c72b",
"section_number": "201",
- "course_reference": "6972f54d6afb10b361a3e8bf",
+ "course_reference": "69cda9299bb0b8cd4cf4c72a",
"section_corequisites": null,
"academic_session": {
"name": "25S",
diff --git a/parser/testdata/case_005/course.json b/parser/testdata/case_005/course.json
index 209bd70..9116fdb 100644
--- a/parser/testdata/case_005/course.json
+++ b/parser/testdata/case_005/course.json
@@ -1,9 +1,9 @@
{
- "_id": "6972f54d6afb10b361a3e8c1",
+ "_id": "69cda9299bb0b8cd4cf4c72c",
"subject_prefix": "AERO",
"course_number": "4320",
"title": "- Laboratory",
- "description": "- ()",
+ "description": "- ()",
"enrollment_reqs": "",
"school": "Undergraduate Studies",
"credit_hours": "4",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8c2"
+ "69cda9299bb0b8cd4cf4c72d"
],
"lecture_contact_hours": "",
"laboratory_contact_hours": "",
diff --git a/parser/testdata/case_005/section.json b/parser/testdata/case_005/section.json
index 247ab4c..d89794b 100644
--- a/parser/testdata/case_005/section.json
+++ b/parser/testdata/case_005/section.json
@@ -1,7 +1,7 @@
{
- "_id": "6972f54d6afb10b361a3e8c2",
+ "_id": "69cda9299bb0b8cd4cf4c72d",
"section_number": "002",
- "course_reference": "6972f54d6afb10b361a3e8c1",
+ "course_reference": "69cda9299bb0b8cd4cf4c72c",
"section_corequisites": null,
"academic_session": {
"name": "25S",
diff --git a/parser/testdata/case_006/course.json b/parser/testdata/case_006/course.json
index a20be42..e11f4c8 100644
--- a/parser/testdata/case_006/course.json
+++ b/parser/testdata/case_006/course.json
@@ -1,9 +1,9 @@
{
- "_id": "6972f54d6afb10b361a3e8c3",
+ "_id": "69cda9299bb0b8cd4cf4c72e",
"subject_prefix": "THEA",
"course_number": "1310",
"title": "Understanding Theatre",
- "description": "THEA 1310 - Understanding Theatre (3 semester credit hours) Lectures, discussions, and performances designed to explore artistic, philosophical, social, historical, and psychological dimensions of the theatrical experience. Topics may include analysis of scripts, the nature of the theater compared to the other performing arts, and the nature of popular entertainments. (3-0) S",
+ "description": "THEA 1310 - Understanding Theatre (3 semester credit hours) Lectures, discussions, and performances designed to explore artistic, philosophical, social, historical, and psychological dimensions of the theatrical experience. Topics may include analysis of scripts, the nature of the theater compared to the other performing arts, and the nature of popular entertainments. (3-0) S",
"enrollment_reqs": "",
"school": "School of Arts, Humanities, and Technology",
"credit_hours": "3",
@@ -15,7 +15,7 @@
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8c4"
+ "69cda9299bb0b8cd4cf4c72f"
],
"lecture_contact_hours": "3",
"laboratory_contact_hours": "0",
diff --git a/parser/testdata/case_006/professors.json b/parser/testdata/case_006/professors.json
index bfde863..57023a3 100644
--- a/parser/testdata/case_006/professors.json
+++ b/parser/testdata/case_006/professors.json
@@ -1,23 +1,23 @@
[
- {
- "_id": "6972f54d6afb10b361a3e8c5",
- "first_name": "Damian",
- "last_name": "Enyaosah",
- "titles": [
- "Primary Instructor"
- ],
- "email": "dpe190000@utdallas.edu",
- "phone_number": "",
- "office": {
- "building": "",
- "room": "",
- "map_uri": ""
- },
- "profile_uri": "",
- "image_uri": "",
- "office_hours": null,
- "sections": [
- "6972f54d6afb10b361a3e8c4"
- ]
- }
+ {
+ "_id": "69cda9299bb0b8cd4cf4c730",
+ "first_name": "Damian",
+ "last_name": "Enyaosah",
+ "titles": [
+ "Primary Instructor"
+ ],
+ "email": "dpe190000@utdallas.edu",
+ "phone_number": "",
+ "office": {
+ "building": "",
+ "room": "",
+ "map_uri": ""
+ },
+ "profile_uri": "",
+ "image_uri": "",
+ "office_hours": null,
+ "sections": [
+ "69cda9299bb0b8cd4cf4c72f"
+ ]
+ }
]
diff --git a/parser/testdata/case_006/section.json b/parser/testdata/case_006/section.json
index 2c57733..6cce905 100644
--- a/parser/testdata/case_006/section.json
+++ b/parser/testdata/case_006/section.json
@@ -1,7 +1,7 @@
{
- "_id": "6972f54d6afb10b361a3e8c4",
+ "_id": "69cda9299bb0b8cd4cf4c72f",
"section_number": "001",
- "course_reference": "6972f54d6afb10b361a3e8c3",
+ "course_reference": "69cda9299bb0b8cd4cf4c72e",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -9,7 +9,7 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "6972f54d6afb10b361a3e8c5"
+ "69cda9299bb0b8cd4cf4c730"
],
"teaching_assistants": [],
"internal_class_number": "24043",
diff --git a/parser/testdata/courses.json b/parser/testdata/courses.json
index df9144e..2808543 100644
--- a/parser/testdata/courses.json
+++ b/parser/testdata/courses.json
@@ -1,152 +1,152 @@
[
{
- "_id": "6972f54d6afb10b361a3e8cb",
- "subject_prefix": "BA",
- "course_number": "1320",
- "title": "Business in a Global World",
- "description": "BA 1320 - Business in a Global World (3 semester credit hours) This course provides a primer on the impacts of globalization on business. We equip students with the basic facts of globalization and examine the business underpinnings and the institutions that shape globalization. We discuss major trends and the future of international management. The aim is an ability to think strategically and critically about global business issues. (3-0) S",
- "enrollment_reqs": "BA 1320 Repeat Restriction",
- "school": "Naveen Jindal School of Management",
- "credit_hours": "3",
+ "_id": "69cda9299bb0b8cd4cf4c73e",
+ "subject_prefix": "AERO",
+ "course_number": "4320",
+ "title": "- Laboratory",
+ "description": "- ()",
+ "enrollment_reqs": "",
+ "school": "Undergraduate Studies",
+ "credit_hours": "4",
"class_level": "Undergraduate",
"activity_type": "Lecture",
"grading": "Graded - Undergraduate",
- "internal_course_number": "015444",
+ "internal_course_number": "000255",
"prerequisites": null,
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8cc"
+ "69cda9299bb0b8cd4cf4c73f"
],
- "lecture_contact_hours": "3",
- "laboratory_contact_hours": "0",
- "offering_frequency": "S",
+ "lecture_contact_hours": "",
+ "laboratory_contact_hours": "",
+ "offering_frequency": "",
"catalog_year": "24",
"attributes": null
},
{
- "_id": "6972f54d6afb10b361a3e8ce",
- "subject_prefix": "BIOL",
- "course_number": "6111",
- "title": "Graduate Research Presentation",
- "description": "BIOL 6111 - Graduate Research Presentation (1 semester credit hour) This course will train graduate students (MS and PhD) in hypothesis building and testing, designing, and conducting experiments, and presenting scientific findings in an efficient and clear manner. During the class, graduate students will discuss and present their graduate research work-in-progress. Significant time outside of class will also be required to analyze data, assemble, and practice presentations. May be repeated for credit as topics vary (2 semester credit hours maximum). Department consent required. (1-0) S",
+ "_id": "69cda9299bb0b8cd4cf4c740",
+ "subject_prefix": "THEA",
+ "course_number": "1310",
+ "title": "Understanding Theatre",
+ "description": "THEA 1310 - Understanding Theatre (3 semester credit hours) Lectures, discussions, and performances designed to explore artistic, philosophical, social, historical, and psychological dimensions of the theatrical experience. Topics may include analysis of scripts, the nature of the theater compared to the other performing arts, and the nature of popular entertainments. (3-0) S",
"enrollment_reqs": "",
- "school": "School of Natural Sciences and Mathematics",
- "credit_hours": "1",
- "class_level": "Graduate",
+ "school": "School of Arts, Humanities, and Technology",
+ "credit_hours": "3",
+ "class_level": "Undergraduate",
"activity_type": "Lecture",
- "grading": "Graded - Graduate",
- "internal_course_number": "016577",
+ "grading": "Graded - Undergraduate",
+ "internal_course_number": "003909",
"prerequisites": null,
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8cf"
+ "69cda9299bb0b8cd4cf4c741"
],
- "lecture_contact_hours": "1",
+ "lecture_contact_hours": "3",
"laboratory_contact_hours": "0",
"offering_frequency": "S",
"catalog_year": "24",
"attributes": null
},
{
- "_id": "6972f54d6afb10b361a3e8d1",
- "subject_prefix": "AERO",
- "course_number": "3320",
- "title": "- Recitation",
- "description": "- ()",
- "enrollment_reqs": "",
- "school": "Undergraduate Studies",
- "credit_hours": "Non-Enroll",
+ "_id": "69cda9299bb0b8cd4cf4c731",
+ "subject_prefix": "ACCT",
+ "course_number": "2301",
+ "title": "Introductory Financial Accounting",
+ "description": "ACCT 2301 - Introductory Financial Accounting (3 semester credit hours) An introduction to financial reporting designed to create an awareness of the accounting concepts and principles for preparing the three basic financial statements: the income statement, balance sheet, and statement of cash flows. A minimum grade of C is required to take upper-division ACCT courses. (3-0) S",
+ "enrollment_reqs": "ACCT 2301 Repeat Restriction",
+ "school": "Naveen Jindal School of Management",
+ "credit_hours": "3",
"class_level": "Undergraduate",
- "activity_type": "Laboratory - No Lab Fee",
+ "activity_type": "Lecture",
"grading": "Graded - Undergraduate",
- "internal_course_number": "000243",
+ "internal_course_number": "000061",
"prerequisites": null,
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8d2"
+ "69cda9299bb0b8cd4cf4c732",
+ "69cda9299bb0b8cd4cf4c735"
],
- "lecture_contact_hours": "",
- "laboratory_contact_hours": "",
- "offering_frequency": "",
+ "lecture_contact_hours": "3",
+ "laboratory_contact_hours": "0",
+ "offering_frequency": "S",
"catalog_year": "24",
"attributes": null
},
{
- "_id": "6972f54d6afb10b361a3e8d3",
- "subject_prefix": "AERO",
- "course_number": "4320",
- "title": "- Laboratory",
- "description": "- ()",
- "enrollment_reqs": "",
- "school": "Undergraduate Studies",
- "credit_hours": "4",
+ "_id": "69cda9299bb0b8cd4cf4c736",
+ "subject_prefix": "BA",
+ "course_number": "1320",
+ "title": "Business in a Global World",
+ "description": "BA 1320 - Business in a Global World (3 semester credit hours) This course provides a primer on the impacts of globalization on business. We equip students with the basic facts of globalization and examine the business underpinnings and the institutions that shape globalization. We discuss major trends and the future of international management. The aim is an ability to think strategically and critically about global business issues. (3-0) S",
+ "enrollment_reqs": "BA 1320 Repeat Restriction",
+ "school": "Naveen Jindal School of Management",
+ "credit_hours": "3",
"class_level": "Undergraduate",
"activity_type": "Lecture",
"grading": "Graded - Undergraduate",
- "internal_course_number": "000255",
+ "internal_course_number": "015444",
"prerequisites": null,
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8d4"
+ "69cda9299bb0b8cd4cf4c737"
],
- "lecture_contact_hours": "",
- "laboratory_contact_hours": "",
- "offering_frequency": "",
+ "lecture_contact_hours": "3",
+ "laboratory_contact_hours": "0",
+ "offering_frequency": "S",
"catalog_year": "24",
"attributes": null
},
{
- "_id": "6972f54d6afb10b361a3e8d5",
- "subject_prefix": "THEA",
- "course_number": "1310",
- "title": "Understanding Theatre",
- "description": "THEA 1310 - Understanding Theatre (3 semester credit hours) Lectures, discussions, and performances designed to explore artistic, philosophical, social, historical, and psychological dimensions of the theatrical experience. Topics may include analysis of scripts, the nature of the theater compared to the other performing arts, and the nature of popular entertainments. (3-0) S",
+ "_id": "69cda9299bb0b8cd4cf4c739",
+ "subject_prefix": "BIOL",
+ "course_number": "6111",
+ "title": "Graduate Research Presentation",
+ "description": "BIOL 6111 - Graduate Research Presentation (1 semester credit hour) This course will train graduate students (MS and PhD) in hypothesis building and testing, designing, and conducting experiments, and presenting scientific findings in an efficient and clear manner. During the class, graduate students will discuss and present their graduate research work-in-progress. Significant time outside of class will also be required to analyze data, assemble, and practice presentations. May be repeated for credit as topics vary (2 semester credit hours maximum). Department consent required. (1-0) S",
"enrollment_reqs": "",
- "school": "School of Arts, Humanities, and Technology",
- "credit_hours": "3",
- "class_level": "Undergraduate",
+ "school": "School of Natural Sciences and Mathematics",
+ "credit_hours": "1",
+ "class_level": "Graduate",
"activity_type": "Lecture",
- "grading": "Graded - Undergraduate",
- "internal_course_number": "003909",
+ "grading": "Graded - Graduate",
+ "internal_course_number": "016577",
"prerequisites": null,
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8d6"
+ "69cda9299bb0b8cd4cf4c73a"
],
- "lecture_contact_hours": "3",
+ "lecture_contact_hours": "1",
"laboratory_contact_hours": "0",
"offering_frequency": "S",
"catalog_year": "24",
"attributes": null
},
{
- "_id": "6972f54d6afb10b361a3e8c6",
- "subject_prefix": "ACCT",
- "course_number": "2301",
- "title": "Introductory Financial Accounting",
- "description": "ACCT 2301 - Introductory Financial Accounting (3 semester credit hours) An introduction to financial reporting designed to create an awareness of the accounting concepts and principles for preparing the three basic financial statements: the income statement, balance sheet, and statement of cash flows. A minimum grade of C is required to take upper-division ACCT courses. (3-0) S",
- "enrollment_reqs": "ACCT 2301 Repeat Restriction",
- "school": "Naveen Jindal School of Management",
- "credit_hours": "3",
+ "_id": "69cda9299bb0b8cd4cf4c73c",
+ "subject_prefix": "AERO",
+ "course_number": "3320",
+ "title": "- Recitation",
+ "description": "- ()",
+ "enrollment_reqs": "",
+ "school": "Undergraduate Studies",
+ "credit_hours": "Non-Enroll",
"class_level": "Undergraduate",
- "activity_type": "Lecture",
+ "activity_type": "Laboratory - No Lab Fee",
"grading": "Graded - Undergraduate",
- "internal_course_number": "000061",
+ "internal_course_number": "000243",
"prerequisites": null,
"corequisites": null,
"co_or_pre_requisites": null,
"sections": [
- "6972f54d6afb10b361a3e8c7",
- "6972f54d6afb10b361a3e8ca"
+ "69cda9299bb0b8cd4cf4c73d"
],
- "lecture_contact_hours": "3",
- "laboratory_contact_hours": "0",
- "offering_frequency": "S",
+ "lecture_contact_hours": "",
+ "laboratory_contact_hours": "",
+ "offering_frequency": "",
"catalog_year": "24",
"attributes": null
}
diff --git a/parser/testdata/professors.json b/parser/testdata/professors.json
index dc66b36..7950af2 100644
--- a/parser/testdata/professors.json
+++ b/parser/testdata/professors.json
@@ -1,6 +1,6 @@
[
{
- "_id": "6972f54d6afb10b361a3e8c9",
+ "_id": "69cda9299bb0b8cd4cf4c734",
"first_name": "Jieying",
"last_name": "Zhang",
"titles": [
@@ -17,12 +17,12 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8c7",
- "6972f54d6afb10b361a3e8ca"
+ "69cda9299bb0b8cd4cf4c732",
+ "69cda9299bb0b8cd4cf4c735"
]
},
{
- "_id": "6972f54d6afb10b361a3e8cd",
+ "_id": "69cda9299bb0b8cd4cf4c738",
"first_name": "Peter",
"last_name": "Lewin",
"titles": [
@@ -39,11 +39,11 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8cc"
+ "69cda9299bb0b8cd4cf4c737"
]
},
{
- "_id": "6972f54d6afb10b361a3e8d0",
+ "_id": "69cda9299bb0b8cd4cf4c73b",
"first_name": "Tian",
"last_name": "Hong",
"titles": [
@@ -60,11 +60,11 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8cf"
+ "69cda9299bb0b8cd4cf4c73a"
]
},
{
- "_id": "6972f54d6afb10b361a3e8d7",
+ "_id": "69cda9299bb0b8cd4cf4c742",
"first_name": "Damian",
"last_name": "Enyaosah",
"titles": [
@@ -81,11 +81,11 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8d6"
+ "69cda9299bb0b8cd4cf4c741"
]
},
{
- "_id": "6972f54d6afb10b361a3e8c8",
+ "_id": "69cda9299bb0b8cd4cf4c733",
"first_name": "Naim Bugra",
"last_name": "Ozel",
"titles": [
@@ -102,8 +102,8 @@
"image_uri": "",
"office_hours": null,
"sections": [
- "6972f54d6afb10b361a3e8c7",
- "6972f54d6afb10b361a3e8ca"
+ "69cda9299bb0b8cd4cf4c732",
+ "69cda9299bb0b8cd4cf4c735"
]
}
]
diff --git a/parser/testdata/sections.json b/parser/testdata/sections.json
index da61c2f..43a472c 100644
--- a/parser/testdata/sections.json
+++ b/parser/testdata/sections.json
@@ -1,8 +1,152 @@
[
{
- "_id": "6972f54d6afb10b361a3e8cc",
+ "_id": "69cda9299bb0b8cd4cf4c732",
+ "section_number": "003",
+ "course_reference": "69cda9299bb0b8cd4cf4c731",
+ "section_corequisites": null,
+ "academic_session": {
+ "name": "25S",
+ "start_date": "2025-01-21T00:00:00-06:00",
+ "end_date": "2025-05-16T00:00:00-05:00"
+ },
+ "professors": [
+ "69cda9299bb0b8cd4cf4c733",
+ "69cda9299bb0b8cd4cf4c734"
+ ],
+ "teaching_assistants": [
+ {
+ "first_name": "Galymzhan",
+ "last_name": "Tazhibayev",
+ "role": "Teaching Assistant",
+ "email": "gxt230023@utdallas.edu"
+ },
+ {
+ "first_name": "Dipta",
+ "last_name": "Banik",
+ "role": "Teaching Assistant",
+ "email": "dxb220047@utdallas.edu"
+ }
+ ],
+ "internal_class_number": "27706",
+ "instruction_mode": "Face-to-Face",
+ "meetings": [
+ {
+ "start_date": "2025-01-21T00:00:00-06:00",
+ "end_date": "2025-05-09T00:00:00-05:00",
+ "meeting_days": [
+ "Tuesday",
+ "Thursday"
+ ],
+ "start_time": "10:00am",
+ "end_time": "11:15am",
+ "modality": "",
+ "location": {
+ "building": "JSOM",
+ "room": "2.717",
+ "map_uri": "https://locator.utdallas.edu/SOM_2.717"
+ }
+ }
+ ],
+ "core_flags": [],
+ "syllabus_uri": "https://dox.utdallas.edu/syl152555",
+ "grade_distribution": [
+ 9,
+ 9,
+ 4,
+ 6,
+ 4,
+ 5,
+ 12,
+ 3,
+ 1,
+ 3,
+ 1,
+ 0,
+ 4,
+ 3,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "attributes": null
+ },
+ {
+ "_id": "69cda9299bb0b8cd4cf4c735",
+ "section_number": "001",
+ "course_reference": "69cda9299bb0b8cd4cf4c731",
+ "section_corequisites": null,
+ "academic_session": {
+ "name": "25S",
+ "start_date": "2025-01-21T00:00:00-06:00",
+ "end_date": "2025-05-16T00:00:00-05:00"
+ },
+ "professors": [
+ "69cda9299bb0b8cd4cf4c734",
+ "69cda9299bb0b8cd4cf4c733"
+ ],
+ "teaching_assistants": [
+ {
+ "first_name": "Galymzhan",
+ "last_name": "Tazhibayev",
+ "role": "Teaching Assistant",
+ "email": "gxt230023@utdallas.edu"
+ },
+ {
+ "first_name": "Dipta",
+ "last_name": "Banik",
+ "role": "Teaching Assistant",
+ "email": "dxb220047@utdallas.edu"
+ }
+ ],
+ "internal_class_number": "26595",
+ "instruction_mode": "Face-to-Face",
+ "meetings": [
+ {
+ "start_date": "2025-01-21T00:00:00-06:00",
+ "end_date": "2025-05-09T00:00:00-05:00",
+ "meeting_days": [
+ "Tuesday",
+ "Thursday"
+ ],
+ "start_time": "8:30am",
+ "end_time": "9:45am",
+ "modality": "",
+ "location": {
+ "building": "JSOM",
+ "room": "2.717",
+ "map_uri": "https://locator.utdallas.edu/SOM_2.717"
+ }
+ }
+ ],
+ "core_flags": [],
+ "syllabus_uri": "https://dox.utdallas.edu/syl152552",
+ "grade_distribution": [
+ 2,
+ 7,
+ 5,
+ 8,
+ 8,
+ 7,
+ 3,
+ 6,
+ 1,
+ 3,
+ 3,
+ 0,
+ 9,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0
+ ],
+ "attributes": null
+ },
+ {
+ "_id": "69cda9299bb0b8cd4cf4c737",
"section_number": "501",
- "course_reference": "6972f54d6afb10b361a3e8cb",
+ "course_reference": "69cda9299bb0b8cd4cf4c736",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -10,7 +154,7 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "6972f54d6afb10b361a3e8cd"
+ "69cda9299bb0b8cd4cf4c738"
],
"teaching_assistants": [
{
@@ -68,9 +212,9 @@
"attributes": null
},
{
- "_id": "6972f54d6afb10b361a3e8cf",
+ "_id": "69cda9299bb0b8cd4cf4c73a",
"section_number": "016",
- "course_reference": "6972f54d6afb10b361a3e8ce",
+ "course_reference": "69cda9299bb0b8cd4cf4c739",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -78,7 +222,7 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "6972f54d6afb10b361a3e8d0"
+ "69cda9299bb0b8cd4cf4c73b"
],
"teaching_assistants": [],
"internal_class_number": "29611",
@@ -106,9 +250,9 @@
"attributes": null
},
{
- "_id": "6972f54d6afb10b361a3e8d2",
+ "_id": "69cda9299bb0b8cd4cf4c73d",
"section_number": "201",
- "course_reference": "6972f54d6afb10b361a3e8d1",
+ "course_reference": "69cda9299bb0b8cd4cf4c73c",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -142,9 +286,9 @@
"attributes": null
},
{
- "_id": "6972f54d6afb10b361a3e8d4",
+ "_id": "69cda9299bb0b8cd4cf4c73f",
"section_number": "002",
- "course_reference": "6972f54d6afb10b361a3e8d3",
+ "course_reference": "69cda9299bb0b8cd4cf4c73e",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -193,9 +337,9 @@
"attributes": null
},
{
- "_id": "6972f54d6afb10b361a3e8d6",
+ "_id": "69cda9299bb0b8cd4cf4c741",
"section_number": "001",
- "course_reference": "6972f54d6afb10b361a3e8d5",
+ "course_reference": "69cda9299bb0b8cd4cf4c740",
"section_corequisites": null,
"academic_session": {
"name": "25S",
@@ -203,7 +347,7 @@
"end_date": "2025-05-16T00:00:00-05:00"
},
"professors": [
- "6972f54d6afb10b361a3e8d7"
+ "69cda9299bb0b8cd4cf4c742"
],
"teaching_assistants": [],
"internal_class_number": "24043",
@@ -252,149 +396,5 @@
0
],
"attributes": null
- },
- {
- "_id": "6972f54d6afb10b361a3e8c7",
- "section_number": "003",
- "course_reference": "6972f54d6afb10b361a3e8c6",
- "section_corequisites": null,
- "academic_session": {
- "name": "25S",
- "start_date": "2025-01-21T00:00:00-06:00",
- "end_date": "2025-05-16T00:00:00-05:00"
- },
- "professors": [
- "6972f54d6afb10b361a3e8c8",
- "6972f54d6afb10b361a3e8c9"
- ],
- "teaching_assistants": [
- {
- "first_name": "Galymzhan",
- "last_name": "Tazhibayev",
- "role": "Teaching Assistant",
- "email": "gxt230023@utdallas.edu"
- },
- {
- "first_name": "Dipta",
- "last_name": "Banik",
- "role": "Teaching Assistant",
- "email": "dxb220047@utdallas.edu"
- }
- ],
- "internal_class_number": "27706",
- "instruction_mode": "Face-to-Face",
- "meetings": [
- {
- "start_date": "2025-01-21T00:00:00-06:00",
- "end_date": "2025-05-09T00:00:00-05:00",
- "meeting_days": [
- "Tuesday",
- "Thursday"
- ],
- "start_time": "10:00am",
- "end_time": "11:15am",
- "modality": "",
- "location": {
- "building": "JSOM",
- "room": "2.717",
- "map_uri": "https://locator.utdallas.edu/SOM_2.717"
- }
- }
- ],
- "core_flags": [],
- "syllabus_uri": "https://dox.utdallas.edu/syl152555",
- "grade_distribution": [
- 9,
- 9,
- 4,
- 6,
- 4,
- 5,
- 12,
- 3,
- 1,
- 3,
- 1,
- 0,
- 4,
- 3,
- 0,
- 0,
- 0,
- 0
- ],
- "attributes": null
- },
- {
- "_id": "6972f54d6afb10b361a3e8ca",
- "section_number": "001",
- "course_reference": "6972f54d6afb10b361a3e8c6",
- "section_corequisites": null,
- "academic_session": {
- "name": "25S",
- "start_date": "2025-01-21T00:00:00-06:00",
- "end_date": "2025-05-16T00:00:00-05:00"
- },
- "professors": [
- "6972f54d6afb10b361a3e8c9",
- "6972f54d6afb10b361a3e8c8"
- ],
- "teaching_assistants": [
- {
- "first_name": "Galymzhan",
- "last_name": "Tazhibayev",
- "role": "Teaching Assistant",
- "email": "gxt230023@utdallas.edu"
- },
- {
- "first_name": "Dipta",
- "last_name": "Banik",
- "role": "Teaching Assistant",
- "email": "dxb220047@utdallas.edu"
- }
- ],
- "internal_class_number": "26595",
- "instruction_mode": "Face-to-Face",
- "meetings": [
- {
- "start_date": "2025-01-21T00:00:00-06:00",
- "end_date": "2025-05-09T00:00:00-05:00",
- "meeting_days": [
- "Tuesday",
- "Thursday"
- ],
- "start_time": "8:30am",
- "end_time": "9:45am",
- "modality": "",
- "location": {
- "building": "JSOM",
- "room": "2.717",
- "map_uri": "https://locator.utdallas.edu/SOM_2.717"
- }
- }
- ],
- "core_flags": [],
- "syllabus_uri": "https://dox.utdallas.edu/syl152552",
- "grade_distribution": [
- 2,
- 7,
- 5,
- 8,
- 8,
- 7,
- 3,
- 6,
- 1,
- 3,
- 3,
- 0,
- 9,
- 1,
- 0,
- 0,
- 0,
- 0
- ],
- "attributes": null
}
]
diff --git a/parser/validator_test.go b/parser/validator_test.go
index 4b623f6..e6a6013 100644
--- a/parser/validator_test.go
+++ b/parser/validator_test.go
@@ -264,11 +264,11 @@ func TestSectionReferenceProfFail(t *testing.T) {
logOutput := logBuffer.String()
for _, msg := range []string{
- "Nonexistent professor reference found for section ID ObjectID(\"6972f54d6afb10b361a3e8c7\")!",
- "Referenced professor ID: ObjectID(\"6972f54d6afb10b361a3e8c9\")",
+ "Nonexistent professor reference found for section ID ObjectID(\"69cda9299bb0b8cd4cf4c732\")!",
+ "Referenced professor ID: ObjectID(\"69cda9299bb0b8cd4cf4c734\")",
} {
if !strings.Contains(logOutput, msg) {
- t.Errorf("The function didn't log correct message. Expected \"%v\"", msg)
+ t.Errorf("The function didn't log correct message. \nExpected \"%v\"\nGot \"%v\"", msg, logOutput)
}
}