Skip to content

Commit 76f3cc5

Browse files
thejus03ravern
andauthored
Several Bug Fixes to Optimiser (#4075)
* fix(module): Fixed the bug from semester timetable data crashing optimiser * fix(UI): Fixed the problem with weirdly spaced info icon for mobiles * fix(lessontype): Typo in lesson type * refactor(optimiser): Adding support for lessons on Saturday * feat(optimiser): add support for special terms in NUSMods shareable link generation * refactor(optimiser): updated time components to handle 30 mins slots - Added auto scroll effect when optimisation is completed * style(optimiser): enhance layout and formatting in OptimiserForm * refactor(optimiser): clarify comments and change to useRef --------- Co-authored-by: Ravern Koh <[email protected]>
1 parent 08191f8 commit 76f3cc5

File tree

11 files changed

+177
-76
lines changed

11 files changed

+177
-76
lines changed

website/api/optimiser/README.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,14 +81,16 @@ The optimiser uses a **Beam Search algorithm** to efficiently explore the vast s
8181
[ /* Tuesday slots */ ],
8282
[ /* Wednesday slots */ ],
8383
[ /* Thursday slots */ ],
84-
[ /* Friday slots */ ]
84+
[ /* Friday slots */ ],
85+
[ /* Saturday slots */ ]
8586
],
8687
"DayDistance": [
8788
0, // Monday
8889
0, // Tuesday
8990
0.6879499381097249, // Wednesday
9091
34.33700778293036, // Thursday
91-
7.738363670499865 // Friday
92+
7.738363670499865, // Friday
93+
0 // Saturday
9294
],
9395
"TotalDistance": 42.76332139153995,
9496
"shareableLink": "https://nusmods.com/timetable/sem-1/share?CS1010S=LEC:1,REC:04&CS2030S=LEC:1&MA1521=LEC:1,TUT:01"
@@ -101,7 +103,7 @@ The optimiser uses a **Beam Search algorithm** to efficiently explore the vast s
101103
| -------------- | ---------- | -------------------------------------------------------------------------------------- |
102104
| `modules` | `[]string` | Module codes to include in optimisation in Upper case (e.g. "CS1010S") |
103105
| `recordings` | `[]string` | Lessons marked as recorded/online (format: "MODULE LessonType") e.g. "CS1010S Lecture" |
104-
| `freeDays` | `[]string` | Weekdays to keep free of physical classes e.g. "Monday" |
106+
| `freeDays` | `[]string` | Days to keep free of physical classes e.g. "Monday" |
105107
| `earliestTime` | `string` | Earliest acceptable class time (HHMM format) |
106108
| `latestTime` | `string` | Latest acceptable class time (HHMM format) |
107109
| `acadYear` | `string` | Academic year (format: "YYYY-YYYY") e.g. "2024-2025" |

website/api/optimiser/_constants/constants.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ var LessonTypeAbbrev = map[string]string{
2121
"PACKAGED TUTORIAL": "PTUT",
2222
"RECITATION": "REC",
2323
"SECTIONAL TEACHING": "SEC",
24-
"SEMINAR-STYLE MODULE TEACHING": "SEM",
24+
"SEMINAR-STYLE MODULE CLASS": "SEM",
2525
"TUTORIAL": "TUT",
2626
"TUTORIAL TYPE 2": "TUT2",
2727
"TUTORIAL TYPE 3": "TUT3",

website/api/optimiser/_models/models.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ type OptimiserRequest struct {
2020

2121
type TimetableState struct {
2222
Assignments map[string]string // lessonKey -> chosen classNo
23-
DaySlots [5][]ModuleSlot // For each weekday, a time-sorted slice of slots
24-
DayDistance [5]float64 // Squared travel distance per day
23+
DaySlots [6][]ModuleSlot // For each day, a time-sorted slice of slots
24+
DayDistance [6]float64 // Squared travel distance per day
2525
TotalDistance float64 // Sum of all DayDistance
2626
}
2727

@@ -37,7 +37,7 @@ type ModuleSlot struct {
3737
// Parsed fields
3838
StartMin int // Minutes from 00:00 (e.g., 540 for 09:00)
3939
EndMin int // Minutes from 00:00
40-
DayIndex int // 0=Monday, 1=Tuesday, 2=Wednesday, 3=Thursday, 4=Friday
40+
DayIndex int // 0=Monday, 1=Tuesday, 2=Wednesday, 3=Thursday, 4=Friday, 5=Saturday
4141
LessonKey string // "MODULE|LessonType"
4242
}
4343

@@ -71,13 +71,14 @@ type Location struct {
7171
Location Coordinates `json:"location"`
7272
}
7373

74-
// dayToIndex maps uppercase weekday names to indices 0..4.
74+
// dayToIndex maps uppercase weekday names to indices 0..5.
7575
var dayToIndex = map[string]int{
7676
"MONDAY": 0,
7777
"TUESDAY": 1,
7878
"WEDNESDAY": 2,
7979
"THURSDAY": 3,
8080
"FRIDAY": 4,
81+
"SATURDAY": 5,
8182
}
8283

8384
// Helper Functions

website/api/optimiser/_modules/modules.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,17 @@ func GetAllModuleSlots(optimiserRequest models.OptimiserRequest) (map[string]map
3939
return nil, err
4040
}
4141

42-
// Store the module slots for the module in map
43-
moduleSlots[module] = mergeAndFilterModuleSlots(moduleData.SemesterData[optimiserRequest.AcadSem-1].Timetable, venues, optimiserRequest, module)
42+
// Get the module timetable for the semester
43+
var moduleTimetable []models.ModuleSlot
44+
for _, semester := range moduleData.SemesterData {
45+
if semester.Semester == optimiserRequest.AcadSem {
46+
moduleTimetable = semester.Timetable
47+
break
48+
}
49+
}
50+
51+
// Store the module slots for the module
52+
moduleSlots[module] = mergeAndFilterModuleSlots(moduleTimetable, venues, optimiserRequest, module)
4453

4554
}
4655

website/api/optimiser/_solver/nusmods_link.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ func GenerateNUSModsShareableLink(assignments map[string]string, req models.Opti
6464
semesterPath = "sem-1"
6565
case 2:
6666
semesterPath = "sem-2"
67+
case 3:
68+
semesterPath = "st-i"
69+
case 4:
70+
semesterPath = "st-ii"
6771
default:
6872
semesterPath = "sem-1"
6973
}

website/api/optimiser/_solver/solver.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ func BeamSearch(
3939
initial := models.TimetableState{
4040
Assignments: make(map[string]string),
4141
}
42-
for d := 0; d < 5; d++ {
42+
for d := 0; d < 6; d++ {
4343
initial.DaySlots[d] = make([]models.ModuleSlot, 0)
4444
}
4545
beam := []models.TimetableState{initial}
@@ -55,7 +55,7 @@ func BeamSearch(
5555

5656
validGroup := make([]models.ModuleSlot, 0, len(group))
5757
for _, slot := range group {
58-
if slot.DayIndex >= 0 && slot.DayIndex < 5 {
58+
if slot.DayIndex >= 0 && slot.DayIndex < 6 {
5959
validGroup = append(validGroup, slot)
6060
}
6161
}
@@ -186,7 +186,7 @@ func copyState(src models.TimetableState) models.TimetableState {
186186
}
187187

188188
// Copy day slots
189-
for i := 0; i < 5; i++ {
189+
for i := 0; i < 6; i++ {
190190
if len(src.DaySlots[i]) > 0 {
191191
newState.DaySlots[i] = make([]models.ModuleSlot, len(src.DaySlots[i]))
192192
copy(newState.DaySlots[i], src.DaySlots[i])
@@ -263,7 +263,7 @@ Lower score means a better (more preferred) timetable.
263263
*/
264264
func scoreTimetableState(state models.TimetableState, recordings map[string]bool, optimiserRequest models.OptimiserRequest) float64 {
265265
var totalScore float64
266-
for d := 0; d < 5; d++ {
266+
for d := 0; d < 6; d++ {
267267
if len(state.DaySlots[d]) == 0 {
268268
continue
269269
}

website/src/views/optimiser/OptimiserButton.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,19 +43,18 @@ const OptimiserButton: React.FC<OptimiserButtonProps> = ({
4343
setIsOptimising(true);
4444
setShareableLink(''); // Reset shareable link
4545
const modulesList = Object.keys(timetable);
46-
const formatTime = (time: string) => `${time.padStart(2, '0')}00`;
4746
const acadYearFormatted = `${acadYear.split('/')[0]}-${acadYear.split('/')[1]}`;
4847

4948
const data = await sendOptimiseRequest(
5049
modulesList,
5150
acadYearFormatted,
5251
activeSemester,
5352
Array.from(selectedFreeDays),
54-
formatTime(earliestTime),
55-
formatTime(latestTime),
53+
earliestTime,
54+
latestTime,
5655
recordings,
57-
formatTime(earliestLunchTime),
58-
formatTime(latestLunchTime),
56+
earliestLunchTime,
57+
latestLunchTime,
5958
);
6059

6160
if (data && data.shareableLink) {

website/src/views/optimiser/OptimiserContent.tsx

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,14 @@ const OptimiserContent: React.FC = () => {
2121

2222
const [selectedLessons, setSelectedLessons] = useState<LessonOption[]>([]);
2323
const [selectedFreeDays, setSelectedFreeDays] = useState<Set<string>>(new Set());
24-
const [earliestTime, setEarliestTime] = useState<string>('08');
25-
const [latestTime, setLatestTime] = useState<string>('19');
26-
const [earliestLunchTime, setEarliestLunchTime] = useState<string>('12');
27-
const [latestLunchTime, setLatestLunchTime] = useState<string>('14');
24+
const [earliestTime, setEarliestTime] = useState<string>('0800');
25+
const [latestTime, setLatestTime] = useState<string>('1900');
26+
const [earliestLunchTime, setEarliestLunchTime] = useState<string>('1200');
27+
const [latestLunchTime, setLatestLunchTime] = useState<string>('1400');
2828
const [freeDayConflicts, setFreeDayConflicts] = useState<FreeDayConflict[]>([]);
2929
const [unAssignedLessons, setUnAssignedLessons] = useState<LessonOption[]>([]);
3030
const [shareableLink, setShareableLink] = useState<string>('');
31+
const [hasSaturday, setHasSaturday] = useState<boolean>(false);
3132

3233
// Generate lesson options from current timetable
3334
const lessonOptions = useMemo(() => {
@@ -76,7 +77,9 @@ const OptimiserContent: React.FC = () => {
7677
lessonsForType.forEach((lesson) => {
7778
days.add(lesson.day);
7879
});
79-
80+
if (days.has('Saturday')) {
81+
setHasSaturday(true);
82+
}
8083
lessonDays.push({
8184
uniqueKey: option.uniqueKey,
8285
moduleCode: option.moduleCode,
@@ -179,6 +182,7 @@ const OptimiserContent: React.FC = () => {
179182
earliestLunchTime={earliestLunchTime}
180183
latestLunchTime={latestLunchTime}
181184
freeDayConflicts={freeDayConflicts}
185+
hasSaturday={hasSaturday}
182186
onToggleLessonSelection={toggleLessonSelection}
183187
onToggleFreeDay={toggleFreeDay}
184188
onEarliestTimeChange={setEarliestTime}

website/src/views/optimiser/OptimiserForm.scss

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
// Section headers
3131
.sectionHeader {
3232
display: flex;
33+
flex-direction: row;
34+
flex-wrap: wrap;
3335
align-items: center;
3436
margin-bottom: 1rem;
3537
font-size: 1rem;
@@ -185,7 +187,7 @@
185187
}
186188

187189
.timeSelect {
188-
width: 4.5rem;
190+
width: 7.1rem;
189191
padding: 0.25rem 0.75rem;
190192
padding-left: 0.9rem;
191193
border: 1px solid var(--gray-lighter);

0 commit comments

Comments
 (0)