Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 8 additions & 2 deletions website/api/optimiser/_models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ import (
"strings"
)

type LessonType = string
type ClassNo = string
type LessonIndex = int
type LessonsByLessonTypeByClassNo = map[LessonType]map[ClassNo][]LessonIndex

type OptimiserRequest struct {
Modules []string `json:"modules"` // Format: ["CS1010S", "CS2030S"]
Recordings []string `json:"recordings"` // Format: ["CS1010S Lecture", "CS2030S Laboratory"]
Expand All @@ -27,14 +32,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 +48,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 @@ -43,6 +43,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
29 changes: 19 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,48 @@ 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
}

// 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, "("+strings.Trim(strings.Join(strings.Fields(fmt.Sprint(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, ";")))
}
}

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
6 changes: 3 additions & 3 deletions website/api/optimiser/_solver/solver.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ func BeamSearch(
// iterate over all slot groups for the current lesson
for i := 0; i < limit; i++ {
group := slotGroups[i]

// Filters out invalid slots by checking if
// DayIndex is not -1 which marks invalid slots when parsing in ParseModuleSlotFields func
validGroup := make([]models.ModuleSlot, 0, len(group))
Expand Down Expand Up @@ -87,7 +87,7 @@ func BeamSearch(
}
}

// if no valid partial timetables found then skip to next lesson
// if no valid partial timetables found then skip to next lesson
// by keeping the beam to create partial timetables
if len(nextBeam) == 0 {
continue
Expand Down Expand Up @@ -394,7 +394,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