Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
32b5f05
Change representation of lesson configs from classNo to lesson indices
zehata Sep 5, 2025
2bcf233
Fix: TA Lessons being highlighted when only classNo matches
zehata Sep 5, 2025
8ef6c06
Fix: Rendering crashes on first load before migration
zehata Sep 6, 2025
b1f27a1
Fix: Empty TA and Hidden module configs do not overwrite current config
zehata Sep 6, 2025
bd1a455
Fix: Imported TA and Hidden module configs not removed after importing
zehata Sep 6, 2025
e0d9ba3
Merge branch 'master' into lesson-groups
zehata Sep 9, 2025
539f662
Merge branch 'master' into lesson-groups
leslieyip02 Oct 18, 2025
04319c9
Refactored share link serialization
zehata Oct 26, 2025
f108705
Removed unused type
zehata Oct 26, 2025
b05656f
Added equality tests for TA modules removal actions
zehata Oct 26, 2025
8137c0a
Modified the name of variables related to the old config format from …
zehata Oct 26, 2025
670ff1d
Removed unnecessary abstraction setLesson
zehata Oct 26, 2025
8a20993
Fix timetable config validation to check all module configs to determ…
zehata Oct 26, 2025
dcc5ae7
Removed unnecessary assignments
zehata Oct 26, 2025
49f3b59
Added tests for hydrating configs with multiple lesson indices of a l…
zehata Oct 26, 2025
3da6123
Added hidden modules to deserialization test string
zehata Oct 26, 2025
d205e80
Renamed getFirstClassNoLessonIndices to getRecoveryLessonIndices to r…
zehata Oct 26, 2025
9200762
Fix missing TA module validation logic and added associated tests
zehata Oct 26, 2025
9f2936d
Modified test name to better reflect its purpose
zehata Oct 26, 2025
6106f4a
Modified return type to use type alias
zehata Oct 26, 2025
f476221
Added guard against empty input in getRecoveryLessonIndices
zehata Oct 26, 2025
4ce7d74
Removed unnecessary/resolved TODOs
zehata Oct 26, 2025
d24b5af
Renamed LessonsByLessonTypeByClassNo to LessonIndicesMap and added an…
zehata Oct 26, 2025
9a9f2c4
Refactored share link serialization to combine TA and hidden modules …
zehata Oct 26, 2025
6c43b1f
Modified deserialization logic to use regex instead of the slicing-an…
zehata Oct 26, 2025
14df7f4
Fix v1 config format used for test case
zehata Oct 26, 2025
7059840
Renamed coloredTimetableLesson to interactableLesson
zehata Oct 26, 2025
1d0abe8
Added missing test to deserialize multiple lessons of the same lesson…
zehata Oct 26, 2025
3f17bea
Modified JSDocs for clarity
zehata Oct 26, 2025
f13e53e
Removed type alias TaModulesConfig
zehata Oct 27, 2025
c8c9f0c
Modified variable names for serialized module lesson configs
zehata Oct 27, 2025
d4784b5
Refactored deserialization logic into smaller functions
zehata Oct 28, 2025
157ef58
Fixed formatting of example input data in documentation
zehata Oct 31, 2025
01bae80
Merge branch 'master' into lesson-groups
zehata Nov 11, 2025
d632f1e
Merge branch 'master' into lesson-groups
leslieyip02 Dec 12, 2025
d790a22
Merge branch 'master' into lesson-groups
leslieyip02 Dec 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions website/api/optimiser/_constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,6 @@ var GAP_PENALTY_THRESHOLD = 120 // 2 hours in minutes
var GAP_PENALTY_RATE = 100.0
var LUNCH_REQUIRED_TIME = 60 // 1 hour in minutes
var CONSECUTIVE_HOURS_PENALTY_RATE = 100

// This is used by [nusmods_link.SerializeLessonIndices] to serialize the result of optimiser into a timetable share link to return to the client
var MODULE_CODE_SEPARATOR = ";"
9 changes: 7 additions & 2 deletions website/api/optimiser/_models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import (
"strings"
)

type LessonType = string
type ClassNo = string
type LessonIndex = int

type OptimiserRequest struct {
Modules []string `json:"modules"` // Format: ["CS1010S", "CS2030S"]
Recordings []string `json:"recordings"` // Format: ["CS1010S Lecture", "CS2030S Laboratory"]
Expand All @@ -27,14 +31,14 @@ type TimetableState struct {
}

type ModuleSlot struct {
ClassNo string `json:"classNo"`
ClassNo ClassNo `json:"classNo"`
Day string `json:"day"`
EndTime string `json:"endTime"`
LessonType string `json:"lessonType"`
StartTime string `json:"startTime"`
Venue string `json:"venue"`
Coordinates Coordinates `json:"coordinates"`
Weeks any `json:"weeks"`
Weeks any `json:"weeks"`

// Parsed fields
StartMin int // Minutes from 00:00 (e.g., 540 for 09:00)
Expand All @@ -43,6 +47,7 @@ type ModuleSlot struct {
LessonKey string // "MODULE|LessonType"
WeeksSet map[int]bool
WeeksString string
LessonIndex LessonIndex
}

// ParseModuleSlotFields parses and populates the parsed fields in ModuleSlot for faster computation
Expand Down
3 changes: 3 additions & 0 deletions website/api/optimiser/_modules/modules.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ func GetAllModuleSlots(optimiserRequest models.OptimiserRequest) (map[string]map
var moduleTimetable []models.ModuleSlot
for _, semester := range moduleData.SemesterData {
if semester.Semester == optimiserRequest.AcadSem {
for lessonIndex := range semester.Timetable {
semester.Timetable[lessonIndex].LessonIndex = lessonIndex
}
moduleTimetable = semester.Timetable
break
}
Expand Down
36 changes: 26 additions & 10 deletions website/api/optimiser/_solver/nusmods_link.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import (
)

// Parses the assignments into a map of module codes to lesson types to class numbers
func CreateConfig(assignments map[string]string) map[string]map[string]string {
config := make(map[string]map[string]string)
func CreateConfig(assignments map[string]string, lessonToSlots map[string][][]models.ModuleSlot) map[string]map[string][]models.LessonIndex {
config := make(map[string]map[string][]models.LessonIndex)

for lessonKey, classNo := range assignments {
// Parse lesson key: "MODULE|LESSONTYPE"
Expand All @@ -23,39 +23,55 @@ func CreateConfig(assignments map[string]string) map[string]map[string]string {

// Initialize module config if not exists
if config[moduleCode] == nil {
config[moduleCode] = make(map[string]string)
config[moduleCode] = make(map[string][]models.LessonIndex)
}

// Add lesson type and class number to config
config[moduleCode][lessonType] = classNo
for _, lessonsWithClassNo := range lessonToSlots[lessonKey] {
if lessonsWithClassNo[0].ClassNo != classNo {
continue
}

for _, lesson := range lessonsWithClassNo {
config[moduleCode][lessonType] = append(config[moduleCode][lessonType], lesson.LessonIndex)
}
break
}
}

return config
}

// Serializes an array of lesson indices into the format used in timetable share links
//
// Returns "1, 2, 3" with input [1, 2, 3]
func SerializeLessonIndices(lessonIndex []models.LessonIndex) string {
return strings.Trim(strings.Join(strings.Fields(fmt.Sprint(lessonIndex)), ","), "[]")
}

// Constructs the URL
func SerializeConfig(config map[string]map[string]string) string {
func SerializeConfig(config map[string]map[string][]models.LessonIndex) string {
var moduleParams []string

for moduleCode, lessons := range config {
var lessonParams []string
for lessonType, classNo := range lessons {
for lessonType, lessonIndex := range lessons {
// Get abbreviation for lesson type
abbrev := constants.LessonTypeAbbrev[strings.ToUpper(lessonType)]

lessonParams = append(lessonParams, fmt.Sprintf("%s:%s", abbrev, classNo))
lessonParams = append(lessonParams, fmt.Sprintf("%s:%s", abbrev, "("+SerializeLessonIndices(lessonIndex)+")"))
}
if len(lessonParams) > 0 {
moduleParams = append(moduleParams, fmt.Sprintf("%s=%s", moduleCode, strings.Join(lessonParams, ",")))
moduleParams = append(moduleParams, fmt.Sprintf("%s=%s", moduleCode, strings.Join(lessonParams, constants.MODULE_CODE_SEPARATOR)))
}
}

return strings.Join(moduleParams, "&")
}

// GenerateNUSModsShareableLink creates a shareable NUSMods link from the assignments
func GenerateNUSModsShareableLink(assignments map[string]string, req models.OptimiserRequest) string {
config := CreateConfig(assignments)
func GenerateNUSModsShareableLink(assignments map[string]string, lessonToSlots map[string][][]models.ModuleSlot, req models.OptimiserRequest) string {
config := CreateConfig(assignments, lessonToSlots)
serializedConfig := SerializeConfig(config)

semesterPath := ""
Expand Down
2 changes: 1 addition & 1 deletion website/api/optimiser/_solver/solver.go
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ func Solve(w http.ResponseWriter, req models.OptimiserRequest) {
})

best := BeamSearch(lessons, lessonToSlots, 2500, 100, recordings, req)
shareableLink := GenerateNUSModsShareableLink(best.Assignments, req)
shareableLink := GenerateNUSModsShareableLink(best.Assignments, lessonToSlots, req)
response := SolveResponse{
TimetableState: best,
ShareableLink: shareableLink,
Expand Down
18 changes: 12 additions & 6 deletions website/src/__mocks__/lessons-array.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"venue": "LT26",
"moduleCode": "CS1010S",
"title": "Programming Methodology",
"colorIndex": 0
"colorIndex": 0,
"lessonIndex": 0
}, {
"classNo": "1",
"lessonType": "Recitation",
Expand All @@ -19,7 +20,8 @@
"venue": "VCRm",
"moduleCode": "CS1010S",
"title": "Programming Methodology",
"colorIndex": 0
"colorIndex": 0,
"lessonIndex": 1
}, {
"classNo": "1",
"lessonType": "Recitation",
Expand All @@ -30,7 +32,8 @@
"venue": "VCRm",
"moduleCode": "CS1010S",
"title": "Programming Methodology",
"colorIndex": 0
"colorIndex": 0,
"lessonIndex": 2
}, {
"classNo": "2",
"lessonType": "Tutorial",
Expand All @@ -41,7 +44,8 @@
"venue": "COM1-0203",
"moduleCode": "CS1010S",
"title": "Programming Methodology",
"colorIndex": 0
"colorIndex": 0,
"lessonIndex": 3
}, {
"classNo": "2",
"lessonType": "Tutorial",
Expand All @@ -52,7 +56,8 @@
"venue": "COM1-0216",
"moduleCode": "CS1010S",
"title": "Programming Methodology",
"colorIndex": 0
"colorIndex": 0,
"lessonIndex": 4
}, {
"classNo": "1",
"lessonType": "Lecture",
Expand All @@ -63,5 +68,6 @@
"venue": "VCRm",
"moduleCode": "CS3216",
"title": "Application Development on Evolving Platforms",
"colorIndex": 1
"colorIndex": 1,
"lessonIndex": 5
}]
Loading