data templates corrected
This commit is contained in:
@@ -5,12 +5,18 @@ five templates/*-wise template modules render from.
|
||||
Scheduling rules (dummy/demo scheduler, not a full CSP solver):
|
||||
1. General courses (shared across every batch of a session) are placed
|
||||
first, only on Tuesday, so those slots are locked in before anything
|
||||
else and never clash across the batches sharing them.
|
||||
2. Lab courses (180-minute / double-slot) are placed next, in lab rooms
|
||||
only, on whichever day/slot-pair is free for the batch, instructor,
|
||||
and room.
|
||||
3. Remaining theory courses fill the rest of the week (Mon/Tue/Wed/Thu/Fri),
|
||||
one 90-minute slot at a time, avoiding room / instructor / batch clashes.
|
||||
else and never clash across the batches sharing them. Each general
|
||||
offering gets `lectures_per_week` sessions.
|
||||
2. Lab courses are placed next: each offering gets `labs_per_week`
|
||||
180-minute / double-slot sessions, in lab rooms only, on whichever
|
||||
day/slot-pair is free for the batch, instructor, and room.
|
||||
3. Remaining theory sessions (`lectures_per_week` per non-general
|
||||
offering) fill the rest of the week (Mon/Tue/Wed/Thu/Fri), one
|
||||
90-minute slot at a time, avoiding room / instructor / batch clashes.
|
||||
|
||||
Room selection always tries a course's preferred lecture_room_id /
|
||||
lab_room_id first, falling back to another same-type room only if the
|
||||
preferred one is busy for that day/slot.
|
||||
"""
|
||||
|
||||
import csv
|
||||
@@ -59,22 +65,32 @@ def load_data():
|
||||
batches_26 = _read_csv(os.path.join("batch_codes", "26", "batches.csv"))
|
||||
batches = batches_25 + batches_26
|
||||
|
||||
courses_by_id = {c["course_id"]: c for c in courses}
|
||||
instructors_by_code = {i["instructor_code"]: i for i in instructors}
|
||||
rooms_by_id = {r["room_id"]: r for r in rooms}
|
||||
instructor_names_by_code = {i["instructor_code"]: i["instructor_name"] for i in instructors}
|
||||
batches_by_code = {b["batch_code"]: b for b in batches}
|
||||
|
||||
# normalize offerings: expand "batch1|batch2" general offerings into a
|
||||
# list of batch codes on each offering record
|
||||
# normalize courses: numeric fields + a lookup by id
|
||||
courses_by_id = {}
|
||||
for c in courses:
|
||||
c["credit_hours"] = int(c["credit_hours"])
|
||||
c["lectures_per_week"] = int(c["lectures_per_week"])
|
||||
c["labs_per_week"] = int(c["labs_per_week"])
|
||||
c["is_general"] = c["is_general"].lower() == "yes"
|
||||
courses_by_id[c["course_id"]] = c
|
||||
|
||||
# normalize offerings: expand "batch1|batch2" section batch groups into
|
||||
# a list of batch codes, and attach the course record for convenience
|
||||
for off in offerings:
|
||||
off["batch_codes"] = off["batch_code"].split("|")
|
||||
off["is_general"] = courses_by_id[off["course_id"]]["is_general"].lower() == "yes"
|
||||
off["course_type"] = courses_by_id[off["course_id"]]["course_type"]
|
||||
off["batch_codes"] = off["batch_codes"].split("|")
|
||||
off["course"] = courses_by_id[off["course_id"]]
|
||||
off["instructor_name"] = instructor_names_by_code.get(off["instructor_code"], off["instructor_code"])
|
||||
|
||||
return {
|
||||
"rooms": rooms,
|
||||
"rooms_by_id": rooms_by_id,
|
||||
"blocks": blocks,
|
||||
"instructors": instructors,
|
||||
"instructors_by_code": instructors_by_code,
|
||||
"instructor_names_by_code": instructor_names_by_code,
|
||||
"courses": courses,
|
||||
"courses_by_id": courses_by_id,
|
||||
"offerings": offerings,
|
||||
@@ -115,10 +131,17 @@ class Scheduler:
|
||||
self.instructor_busy.setdefault(key, set()).add(instructor_code)
|
||||
self.batch_busy.setdefault(key, set()).update(batch_codes)
|
||||
|
||||
def _room_candidates(self, preferred_room_id, room_pool):
|
||||
"""Preferred room first, then the rest of the same-type pool."""
|
||||
preferred = self.data["rooms_by_id"].get(preferred_room_id)
|
||||
rest = [r for r in room_pool if r["room_id"] != preferred_room_id]
|
||||
return ([preferred] if preferred else []) + rest
|
||||
|
||||
def _add_session(self, offering, day, slot_indices, room):
|
||||
course = self.data["courses_by_id"][offering["course_id"]]
|
||||
course = offering["course"]
|
||||
start = TIME_SLOTS[slot_indices[0]]["start"]
|
||||
end = TIME_SLOTS[slot_indices[-1]]["end"]
|
||||
session_type = "lab" if len(slot_indices) > 1 else "theory"
|
||||
self.sessions.append({
|
||||
"day": day,
|
||||
"slot_indices": slot_indices,
|
||||
@@ -126,71 +149,88 @@ class Scheduler:
|
||||
"end": end,
|
||||
"course_id": offering["course_id"],
|
||||
"course_name": course["course_name"],
|
||||
"course_type": course["course_type"],
|
||||
"is_general": offering["is_general"],
|
||||
"course_type": session_type,
|
||||
"is_general": course["is_general"],
|
||||
"section_id": offering["section_id"],
|
||||
"instructor_code": offering["instructor_code"],
|
||||
"instructor_name": self.data["instructors_by_code"][offering["instructor_code"]]["instructor_name"],
|
||||
"instructor_name": offering["instructor_name"],
|
||||
"room_id": room["room_id"],
|
||||
"room_name": room["room_name"],
|
||||
"batch_codes": offering["batch_codes"],
|
||||
})
|
||||
|
||||
def schedule_general_courses(self):
|
||||
day = "Tuesday"
|
||||
general = [o for o in self.data["offerings"] if o["is_general"]]
|
||||
general = [o for o in self.data["offerings"] if o["course"]["is_general"]]
|
||||
for offering in general:
|
||||
placed = False
|
||||
for slot_index in TEACHING_SLOT_INDICES:
|
||||
for room in self.classrooms:
|
||||
if self._is_free(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"]):
|
||||
self._mark_busy(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
||||
self._add_session(offering, day, [slot_index], room)
|
||||
placed = True
|
||||
course = offering["course"]
|
||||
room_candidates = self._room_candidates(course["lecture_room_id"], self.classrooms)
|
||||
|
||||
for _ in range(course["lectures_per_week"]):
|
||||
placed = False
|
||||
# Tuesday first, then spill to the rest of the week if a
|
||||
# session can't fit there.
|
||||
for day in ["Tuesday"] + [d for d in DAYS if d != "Tuesday"]:
|
||||
for slot_index in TEACHING_SLOT_INDICES:
|
||||
for room in room_candidates:
|
||||
if self._is_free(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"]):
|
||||
self._mark_busy(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
||||
self._add_session(offering, day, [slot_index], room)
|
||||
placed = True
|
||||
break
|
||||
if placed:
|
||||
break
|
||||
if placed:
|
||||
break
|
||||
if placed:
|
||||
break
|
||||
if not placed:
|
||||
print(f"WARNING: could not place general course {offering['course_id']}")
|
||||
if not placed:
|
||||
print(f"WARNING: could not place a lecture for general course {offering['course_id']} section {offering['section_id']}")
|
||||
|
||||
def schedule_lab_courses(self):
|
||||
labs_offerings = [o for o in self.data["offerings"] if o["course_type"] == "lab" and not o["is_general"]]
|
||||
for offering in labs_offerings:
|
||||
placed = False
|
||||
for day in DAYS:
|
||||
for slot_a, slot_b in LAB_SLOT_PAIRS:
|
||||
for room in self.labs:
|
||||
if self._is_free(day, slot_a, room["room_id"], offering["instructor_code"], offering["batch_codes"]) and \
|
||||
self._is_free(day, slot_b, room["room_id"], offering["instructor_code"], offering["batch_codes"]):
|
||||
self._mark_busy(day, slot_a, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
||||
self._mark_busy(day, slot_b, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
||||
self._add_session(offering, day, [slot_a, slot_b], room)
|
||||
placed = True
|
||||
offerings = [o for o in self.data["offerings"] if not o["course"]["is_general"] and o["course"]["labs_per_week"] > 0]
|
||||
for offering in offerings:
|
||||
course = offering["course"]
|
||||
room_candidates = self._room_candidates(course["lab_room_id"], self.labs)
|
||||
|
||||
for _ in range(course["labs_per_week"]):
|
||||
placed = False
|
||||
for day in DAYS:
|
||||
for slot_a, slot_b in LAB_SLOT_PAIRS:
|
||||
for room in room_candidates:
|
||||
if self._is_free(day, slot_a, room["room_id"], offering["instructor_code"], offering["batch_codes"]) and \
|
||||
self._is_free(day, slot_b, room["room_id"], offering["instructor_code"], offering["batch_codes"]):
|
||||
self._mark_busy(day, slot_a, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
||||
self._mark_busy(day, slot_b, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
||||
self._add_session(offering, day, [slot_a, slot_b], room)
|
||||
placed = True
|
||||
break
|
||||
if placed:
|
||||
break
|
||||
if placed:
|
||||
break
|
||||
if placed:
|
||||
break
|
||||
if not placed:
|
||||
print(f"WARNING: could not place lab course {offering['course_id']} for {offering['batch_codes']}")
|
||||
if not placed:
|
||||
print(f"WARNING: could not place a lab for course {offering['course_id']} section {offering['section_id']} ({offering['batch_codes']})")
|
||||
|
||||
def schedule_theory_courses(self):
|
||||
theory_offerings = [o for o in self.data["offerings"] if o["course_type"] == "theory" and not o["is_general"]]
|
||||
for offering in theory_offerings:
|
||||
placed = False
|
||||
for day in DAYS:
|
||||
for slot_index in TEACHING_SLOT_INDICES:
|
||||
for room in self.classrooms:
|
||||
if self._is_free(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"]):
|
||||
self._mark_busy(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
||||
self._add_session(offering, day, [slot_index], room)
|
||||
placed = True
|
||||
offerings = [o for o in self.data["offerings"] if not o["course"]["is_general"]]
|
||||
for offering in offerings:
|
||||
course = offering["course"]
|
||||
room_candidates = self._room_candidates(course["lecture_room_id"], self.classrooms)
|
||||
|
||||
for _ in range(course["lectures_per_week"]):
|
||||
placed = False
|
||||
for day in DAYS:
|
||||
for slot_index in TEACHING_SLOT_INDICES:
|
||||
for room in room_candidates:
|
||||
if self._is_free(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"]):
|
||||
self._mark_busy(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
||||
self._add_session(offering, day, [slot_index], room)
|
||||
placed = True
|
||||
break
|
||||
if placed:
|
||||
break
|
||||
if placed:
|
||||
break
|
||||
if placed:
|
||||
break
|
||||
if not placed:
|
||||
print(f"WARNING: could not place theory course {offering['course_id']} for {offering['batch_codes']}")
|
||||
if not placed:
|
||||
print(f"WARNING: could not place a lecture for course {offering['course_id']} section {offering['section_id']} ({offering['batch_codes']})")
|
||||
|
||||
def run(self):
|
||||
self.schedule_general_courses()
|
||||
|
||||
Reference in New Issue
Block a user