Skip to content

Commit f8cf8d9

Browse files
committed
Split up the endpoints into different files
1 parent 1945f6d commit f8cf8d9

File tree

5 files changed

+359
-352
lines changed

5 files changed

+359
-352
lines changed

course-matrix/backend/src/controllers/generatorController.ts

Lines changed: 6 additions & 352 deletions
Original file line numberDiff line numberDiff line change
@@ -1,365 +1,19 @@
11
import exp from "constants";
22
import { Request, Response } from "express";
33

4-
import { supabase } from "../db/setupDb"; // Supabase instance for database interactions
5-
import asyncHandler from "../middleware/asyncHandler"; // Middleware to handle async route handlers
6-
7-
// Interface to define the structure of an Offering
8-
export interface Offering {
9-
id: number;
10-
course_id: number;
11-
meeting_section: string;
12-
offering: string;
13-
day: string;
14-
start: string;
15-
end: string;
16-
location: string;
17-
current: number;
18-
max: number;
19-
is_waitlisted: boolean;
20-
delivery_mode: string;
21-
instructor: string;
22-
notes: string;
23-
code: string;
24-
}
25-
26-
// Utility function to create an Offering object with optional overrides
27-
export function createOffering(overrides: Partial<Offering> = {}): Offering {
28-
return {
29-
id: overrides.id ?? -1,
30-
course_id: overrides.course_id ?? -1,
31-
meeting_section: overrides.meeting_section ?? "No Section",
32-
offering: overrides.offering ?? "No Offering",
33-
day: overrides.day ?? "N/A",
34-
start: overrides.start ?? "00:00:00",
35-
end: overrides.end ?? "00:00:00",
36-
location: overrides.location ?? "No Room",
37-
current: overrides.current ?? -1,
38-
max: overrides.max ?? -1,
39-
is_waitlisted: overrides.is_waitlisted ?? false,
40-
delivery_mode: overrides.delivery_mode ?? "N/A",
41-
instructor: overrides.instructor ?? "N/A",
42-
notes: overrides.notes ?? "N/A",
43-
code: overrides.code ?? "N/A",
44-
};
45-
}
46-
47-
// Enum to define different types of restrictions for offerings
48-
export enum RestrictionType {
49-
RestrictBefore = "Restrict Before",
50-
RestrictAfter = "Restrict After",
51-
RestrictBetween = "Restrict Between",
52-
RestrictDay = "Restrict Day",
53-
RestrictDaysOff = "Days Off",
54-
}
55-
56-
// Interface for the restriction object
57-
export interface Restriction {
58-
type: RestrictionType;
59-
days: string[];
60-
startTime: string;
61-
endTime: string;
62-
disabled: boolean;
63-
numDays: number;
64-
}
65-
66-
// Interface for organizing offerings with the same meeting_section together
67-
export interface GroupedOfferingList {
68-
course_id: number;
69-
groups: Record<string, Offering[]>;
70-
}
71-
72-
// Interface for organizing offerings by course ID
73-
export interface OfferingList {
74-
course_id: number;
75-
offerings: Offering[];
76-
}
77-
78-
// Interface for organizing offerings by course ID and the category of the
79-
// course (LEC, TUT, PRA)
80-
export interface CategorizedOfferingList {
81-
course_id: number;
82-
category: "LEC" | "TUT" | "PRA";
83-
offerings: Record<string, Offering[]>;
84-
}
85-
86-
// Function to fetch offerings from the database for a given course and semester
87-
export async function getOfferings(course_id: number, semester: string) {
88-
let { data: offeringData, error: offeringError } = await supabase
89-
.schema("course")
90-
.from("offerings")
91-
.select(
92-
`
93-
id,
94-
course_id,
95-
meeting_section,
96-
offering,
97-
day,
98-
start,
99-
end,
100-
location,
101-
current,
102-
max,
103-
is_waitlisted,
104-
delivery_mode,
105-
instructor,
106-
notes,
107-
code
108-
`,
109-
)
110-
.eq("course_id", course_id)
111-
.eq("offering", semester);
112-
113-
return offeringData;
114-
}
115-
116-
// Function to group offerings with the same meeting section together
117-
export async function groupOfferings(courseOfferingsList: OfferingList[]) {
118-
const groupedOfferingsList: GroupedOfferingList[] = [];
119-
for (const offering of courseOfferingsList) {
120-
const groupedOfferings: GroupedOfferingList = {
121-
course_id: offering.course_id,
122-
groups: {},
123-
};
124-
offering.offerings.forEach((offering) => {
125-
if (!groupedOfferings.groups[offering.meeting_section]) {
126-
groupedOfferings.groups[offering.meeting_section] = [];
127-
}
128-
groupedOfferings.groups[offering.meeting_section].push(offering);
129-
});
130-
groupedOfferingsList.push(groupedOfferings);
131-
}
132-
133-
return groupedOfferingsList;
134-
}
135-
136-
// Function to get the maximum number of days allowed based on restrictions
137-
export async function getMaxDays(restrictions: Restriction[]) {
138-
for (const restriction of restrictions) {
139-
if (restriction.disabled) continue;
140-
if (restriction.type == RestrictionType.RestrictDaysOff) {
141-
return 5 - restriction.numDays; // Subtract the restricted days from the total days
142-
}
143-
}
144-
return 5; // Default to 5 days if no restrictions
145-
}
4+
import {Offering, OfferingList, GroupedOfferingList} from "../types/generatorTypes"
5+
import {getMaxDays, groupOfferings, getValidOfferings, categorizeValidOfferings, trim} from "../utils/generatorHelpers"
6+
import getOfferings from "../services/getOfferings";
7+
import { getValidSchedules } from "../services/getValidSchedules";
1468

147-
// Function to check if an offering satisfies the restrictions
148-
export function isValidOffering(
149-
offering: Offering,
150-
restrictions: Restriction[],
151-
) {
152-
for (const restriction of restrictions) {
153-
if (restriction.disabled) continue;
154-
if (!restriction.days.includes(offering.day)) continue;
155-
// Check based on the restriction type
156-
switch (restriction.type) {
157-
case RestrictionType.RestrictBefore:
158-
if (offering.start < restriction.endTime) return false;
159-
break;
160-
161-
case RestrictionType.RestrictAfter:
162-
console.log("====");
163-
console.log(offering.end);
164-
console.log(restriction.endTime);
165-
if (offering.end > restriction.startTime) return false;
166-
break;
167-
168-
case RestrictionType.RestrictBetween:
169-
if (
170-
offering.start < restriction.endTime &&
171-
restriction.startTime < offering.end
172-
) {
173-
return false;
174-
}
175-
break;
176-
177-
case RestrictionType.RestrictDay:
178-
if (restriction.days.includes(offering.day)) {
179-
return false;
180-
}
181-
break;
182-
}
183-
}
184-
185-
console.log(offering);
186-
return true;
187-
}
188-
189-
// Function to get valid offerings by filtering them based on the restrictions
190-
export async function getValidOfferings(
191-
groups: Record<string, Offering[]>,
192-
restrictions: Restriction[],
193-
) {
194-
const validGroups: Record<string, Offering[]> = {};
195-
196-
// Loop through each group in the groups object
197-
for (const [groupKey, offerings] of Object.entries(groups)) {
198-
// Check if all offerings in the group are valid
199-
const allValid = offerings.every((offering) =>
200-
isValidOffering(offering, restrictions),
201-
);
202-
203-
// Only add the group to validGroups if all offerings are valid
204-
if (allValid) {
205-
validGroups[groupKey] = offerings;
206-
}
207-
}
208-
209-
// Return the object with valid groups
210-
return validGroups;
211-
}
212-
213-
// Function to categorize offerings into lectures, tutorials, and practicals
214-
export async function categorizeValidOfferings(
215-
offerings: GroupedOfferingList[],
216-
) {
217-
const lst: CategorizedOfferingList[] = [];
218-
219-
for (const offering of offerings) {
220-
const lectures: CategorizedOfferingList = {
221-
course_id: offering.course_id,
222-
category: "LEC",
223-
offerings: {},
224-
};
225-
const tutorials: CategorizedOfferingList = {
226-
course_id: offering.course_id,
227-
category: "TUT",
228-
offerings: {},
229-
};
230-
const practicals: CategorizedOfferingList = {
231-
course_id: offering.course_id,
232-
category: "PRA",
233-
offerings: {},
234-
};
235-
236-
for (const [meeting_section, offerings] of Object.entries(
237-
offering.groups,
238-
)) {
239-
if (meeting_section && meeting_section.startsWith("PRA")) {
240-
practicals.offerings[meeting_section] = offerings;
241-
} else if (meeting_section && meeting_section.startsWith("TUT")) {
242-
tutorials.offerings[meeting_section] = offerings;
243-
} else {
244-
lectures.offerings[meeting_section] = offerings;
245-
}
246-
}
247-
248-
for (const x of [lectures, practicals, tutorials]) {
249-
if (Object.keys(x.offerings).length > 0) {
250-
lst.push(x);
251-
}
252-
}
253-
}
254-
return lst;
255-
}
256-
257-
// Function to check if an offering can be inserted into the current list of
258-
// offerings without conflicts
259-
export async function canInsert(toInsert: Offering, curList: Offering[]) {
260-
for (const offering of curList) {
261-
if (offering.day == toInsert.day) {
262-
if (offering.start < toInsert.end && toInsert.start < offering.end) {
263-
return false; // Check if the time overlaps
264-
}
265-
}
266-
}
267-
268-
return true; // No conflict found
269-
}
270-
271-
// Function to check if an ever offerings in toInstList can be inserted into
272-
// the current list of offerings without conflicts
273-
export async function canInsertList(
274-
toInsertList: Offering[],
275-
curList: Offering[],
276-
) {
277-
console.log(toInsertList);
278-
return toInsertList.every((x) => canInsert(x, curList));
279-
}
280-
281-
// Function to generate a frequency table of days from a list of offerings
282-
export function getFrequencyTable(arr: Offering[]): Map<string, number> {
283-
const freqMap = new Map<string, number>();
284-
285-
for (const item of arr) {
286-
const count = freqMap.get(item.day) || 0;
287-
freqMap.set(item.day, count + 1);
288-
}
289-
return freqMap;
290-
}
291-
292-
// Function to generate all valid schedules based on offerings and restrictions
293-
294-
export async function getValidSchedules(
295-
validSchedules: Offering[][],
296-
courseOfferingsList: CategorizedOfferingList[],
297-
curList: Offering[],
298-
cur: number,
299-
len: number,
300-
maxdays: number,
301-
) {
302-
// Base case: if all courses have been considered
303-
if (cur == len) {
304-
const freq: Map<string, number> = getFrequencyTable(curList);
305-
306-
// If the number of unique days is within the allowed limit, add the current
307-
// schedule to the list
308-
if (freq.size <= maxdays) {
309-
validSchedules.push([...curList]); // Push a copy of the current list
310-
}
311-
return;
312-
}
313-
314-
const offeringsForCourse = courseOfferingsList[cur];
315-
316-
// Recursively attempt to add offerings for the current course
317-
for (const [groupKey, offerings] of Object.entries(
318-
offeringsForCourse.offerings,
319-
)) {
320-
if (await canInsertList(offerings, curList)) {
321-
const count = offerings.length;
322-
curList.push(...offerings); // Add offering to the current list
323-
324-
// Recursively generate schedules for the next course
325-
await getValidSchedules(
326-
validSchedules,
327-
courseOfferingsList,
328-
curList,
329-
cur + 1,
330-
len,
331-
maxdays,
332-
);
333-
334-
// Backtrack: remove the last offering if no valid schedule was found
335-
for (let i = 0; i < count; i++) curList.pop();
336-
}
337-
}
338-
}
339-
340-
// Trims the list of scheules to only return 10 random schedule if there is more
341-
// than 10 available options.
342-
export function trim(schedules: Offering[][]) {
343-
if (schedules.length <= 10) return schedules;
344-
const num = schedules.length;
345-
346-
const uniqueNumbers = new Set<number>();
347-
while (uniqueNumbers.size < 10) {
348-
uniqueNumbers.add(Math.floor(Math.random() * num));
349-
}
350-
// console.log(uniqueNumbers);
351-
const trim_schedule: Offering[][] = [];
352-
for (const value of uniqueNumbers) trim_schedule.push(schedules[value]);
353-
354-
return trim_schedule;
355-
}
9+
import asyncHandler from "../middleware/asyncHandler"; // Middleware to handle async route handlers
35610

35711
// Express route handler to generate timetables based on user input
35812
export default {
35913
generateTimetable: asyncHandler(async (req: Request, res: Response) => {
36014
try {
36115
// Extract event details and course information from the request
362-
const { name, date, semester, search, courses, restrictions } = req.body;
16+
const {semester, courses, restrictions } = req.body;
36317
const courseOfferingsList: OfferingList[] = [];
36418
const validCourseOfferingsList: GroupedOfferingList[] = [];
36519
const maxdays = await getMaxDays(restrictions);

0 commit comments

Comments
 (0)