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):
|
Scheduling rules (dummy/demo scheduler, not a full CSP solver):
|
||||||
1. General courses (shared across every batch of a session) are placed
|
1. General courses (shared across every batch of a session) are placed
|
||||||
first, only on Tuesday, so those slots are locked in before anything
|
first, only on Tuesday, so those slots are locked in before anything
|
||||||
else and never clash across the batches sharing them.
|
else and never clash across the batches sharing them. Each general
|
||||||
2. Lab courses (180-minute / double-slot) are placed next, in lab rooms
|
offering gets `lectures_per_week` sessions.
|
||||||
only, on whichever day/slot-pair is free for the batch, instructor,
|
2. Lab courses are placed next: each offering gets `labs_per_week`
|
||||||
and room.
|
180-minute / double-slot sessions, in lab rooms only, on whichever
|
||||||
3. Remaining theory courses fill the rest of the week (Mon/Tue/Wed/Thu/Fri),
|
day/slot-pair is free for the batch, instructor, and room.
|
||||||
one 90-minute slot at a time, avoiding room / instructor / batch clashes.
|
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
|
import csv
|
||||||
@@ -59,22 +65,32 @@ def load_data():
|
|||||||
batches_26 = _read_csv(os.path.join("batch_codes", "26", "batches.csv"))
|
batches_26 = _read_csv(os.path.join("batch_codes", "26", "batches.csv"))
|
||||||
batches = batches_25 + batches_26
|
batches = batches_25 + batches_26
|
||||||
|
|
||||||
courses_by_id = {c["course_id"]: c for c in courses}
|
rooms_by_id = {r["room_id"]: r for r in rooms}
|
||||||
instructors_by_code = {i["instructor_code"]: i for i in instructors}
|
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}
|
batches_by_code = {b["batch_code"]: b for b in batches}
|
||||||
|
|
||||||
# normalize offerings: expand "batch1|batch2" general offerings into a
|
# normalize courses: numeric fields + a lookup by id
|
||||||
# list of batch codes on each offering record
|
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:
|
for off in offerings:
|
||||||
off["batch_codes"] = off["batch_code"].split("|")
|
off["batch_codes"] = off["batch_codes"].split("|")
|
||||||
off["is_general"] = courses_by_id[off["course_id"]]["is_general"].lower() == "yes"
|
off["course"] = courses_by_id[off["course_id"]]
|
||||||
off["course_type"] = courses_by_id[off["course_id"]]["course_type"]
|
off["instructor_name"] = instructor_names_by_code.get(off["instructor_code"], off["instructor_code"])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"rooms": rooms,
|
"rooms": rooms,
|
||||||
|
"rooms_by_id": rooms_by_id,
|
||||||
"blocks": blocks,
|
"blocks": blocks,
|
||||||
"instructors": instructors,
|
"instructors": instructors,
|
||||||
"instructors_by_code": instructors_by_code,
|
"instructor_names_by_code": instructor_names_by_code,
|
||||||
"courses": courses,
|
"courses": courses,
|
||||||
"courses_by_id": courses_by_id,
|
"courses_by_id": courses_by_id,
|
||||||
"offerings": offerings,
|
"offerings": offerings,
|
||||||
@@ -115,10 +131,17 @@ class Scheduler:
|
|||||||
self.instructor_busy.setdefault(key, set()).add(instructor_code)
|
self.instructor_busy.setdefault(key, set()).add(instructor_code)
|
||||||
self.batch_busy.setdefault(key, set()).update(batch_codes)
|
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):
|
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"]
|
start = TIME_SLOTS[slot_indices[0]]["start"]
|
||||||
end = TIME_SLOTS[slot_indices[-1]]["end"]
|
end = TIME_SLOTS[slot_indices[-1]]["end"]
|
||||||
|
session_type = "lab" if len(slot_indices) > 1 else "theory"
|
||||||
self.sessions.append({
|
self.sessions.append({
|
||||||
"day": day,
|
"day": day,
|
||||||
"slot_indices": slot_indices,
|
"slot_indices": slot_indices,
|
||||||
@@ -126,71 +149,88 @@ class Scheduler:
|
|||||||
"end": end,
|
"end": end,
|
||||||
"course_id": offering["course_id"],
|
"course_id": offering["course_id"],
|
||||||
"course_name": course["course_name"],
|
"course_name": course["course_name"],
|
||||||
"course_type": course["course_type"],
|
"course_type": session_type,
|
||||||
"is_general": offering["is_general"],
|
"is_general": course["is_general"],
|
||||||
|
"section_id": offering["section_id"],
|
||||||
"instructor_code": offering["instructor_code"],
|
"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_id": room["room_id"],
|
||||||
"room_name": room["room_name"],
|
"room_name": room["room_name"],
|
||||||
"batch_codes": offering["batch_codes"],
|
"batch_codes": offering["batch_codes"],
|
||||||
})
|
})
|
||||||
|
|
||||||
def schedule_general_courses(self):
|
def schedule_general_courses(self):
|
||||||
day = "Tuesday"
|
general = [o for o in self.data["offerings"] if o["course"]["is_general"]]
|
||||||
general = [o for o in self.data["offerings"] if o["is_general"]]
|
|
||||||
for offering in general:
|
for offering in general:
|
||||||
placed = False
|
course = offering["course"]
|
||||||
for slot_index in TEACHING_SLOT_INDICES:
|
room_candidates = self._room_candidates(course["lecture_room_id"], self.classrooms)
|
||||||
for room in self.classrooms:
|
|
||||||
if self._is_free(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"]):
|
for _ in range(course["lectures_per_week"]):
|
||||||
self._mark_busy(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
placed = False
|
||||||
self._add_session(offering, day, [slot_index], room)
|
# Tuesday first, then spill to the rest of the week if a
|
||||||
placed = True
|
# 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
|
break
|
||||||
if placed:
|
if not placed:
|
||||||
break
|
print(f"WARNING: could not place a lecture for general course {offering['course_id']} section {offering['section_id']}")
|
||||||
if not placed:
|
|
||||||
print(f"WARNING: could not place general course {offering['course_id']}")
|
|
||||||
|
|
||||||
def schedule_lab_courses(self):
|
def schedule_lab_courses(self):
|
||||||
labs_offerings = [o for o in self.data["offerings"] if o["course_type"] == "lab" and not o["is_general"]]
|
offerings = [o for o in self.data["offerings"] if not o["course"]["is_general"] and o["course"]["labs_per_week"] > 0]
|
||||||
for offering in labs_offerings:
|
for offering in offerings:
|
||||||
placed = False
|
course = offering["course"]
|
||||||
for day in DAYS:
|
room_candidates = self._room_candidates(course["lab_room_id"], self.labs)
|
||||||
for slot_a, slot_b in LAB_SLOT_PAIRS:
|
|
||||||
for room in self.labs:
|
for _ in range(course["labs_per_week"]):
|
||||||
if self._is_free(day, slot_a, room["room_id"], offering["instructor_code"], offering["batch_codes"]) and \
|
placed = False
|
||||||
self._is_free(day, slot_b, room["room_id"], offering["instructor_code"], offering["batch_codes"]):
|
for day in DAYS:
|
||||||
self._mark_busy(day, slot_a, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
for slot_a, slot_b in LAB_SLOT_PAIRS:
|
||||||
self._mark_busy(day, slot_b, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
for room in room_candidates:
|
||||||
self._add_session(offering, day, [slot_a, slot_b], room)
|
if self._is_free(day, slot_a, room["room_id"], offering["instructor_code"], offering["batch_codes"]) and \
|
||||||
placed = True
|
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
|
break
|
||||||
if placed:
|
if placed:
|
||||||
break
|
break
|
||||||
if placed:
|
if not placed:
|
||||||
break
|
print(f"WARNING: could not place a lab for course {offering['course_id']} section {offering['section_id']} ({offering['batch_codes']})")
|
||||||
if not placed:
|
|
||||||
print(f"WARNING: could not place lab course {offering['course_id']} for {offering['batch_codes']}")
|
|
||||||
|
|
||||||
def schedule_theory_courses(self):
|
def schedule_theory_courses(self):
|
||||||
theory_offerings = [o for o in self.data["offerings"] if o["course_type"] == "theory" and not o["is_general"]]
|
offerings = [o for o in self.data["offerings"] if not o["course"]["is_general"]]
|
||||||
for offering in theory_offerings:
|
for offering in offerings:
|
||||||
placed = False
|
course = offering["course"]
|
||||||
for day in DAYS:
|
room_candidates = self._room_candidates(course["lecture_room_id"], self.classrooms)
|
||||||
for slot_index in TEACHING_SLOT_INDICES:
|
|
||||||
for room in self.classrooms:
|
for _ in range(course["lectures_per_week"]):
|
||||||
if self._is_free(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"]):
|
placed = False
|
||||||
self._mark_busy(day, slot_index, room["room_id"], offering["instructor_code"], offering["batch_codes"])
|
for day in DAYS:
|
||||||
self._add_session(offering, day, [slot_index], room)
|
for slot_index in TEACHING_SLOT_INDICES:
|
||||||
placed = True
|
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
|
break
|
||||||
if placed:
|
if placed:
|
||||||
break
|
break
|
||||||
if placed:
|
if not placed:
|
||||||
break
|
print(f"WARNING: could not place a lecture for course {offering['course_id']} section {offering['section_id']} ({offering['batch_codes']})")
|
||||||
if not placed:
|
|
||||||
print(f"WARNING: could not place theory course {offering['course_id']} for {offering['batch_codes']}")
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.schedule_general_courses()
|
self.schedule_general_courses()
|
||||||
|
|||||||
@@ -1,25 +1,15 @@
|
|||||||
course_id,batch_code,instructor_code
|
course_id,section_id,batch_codes,instructor_code
|
||||||
CS101,25021519,1001
|
CS101,A,25021519,1001
|
||||||
CS102,25021519,1001
|
CS201,A,25021519,1002
|
||||||
CS201,25021519,1002
|
CS101,B,25021520,1001
|
||||||
CS202,25021519,1002
|
CS201,B,25021520,1002
|
||||||
CS101,25021520,1001
|
EE101,A,25031519,1004
|
||||||
CS102,25021520,1001
|
CS101,C,26021519,1001
|
||||||
CS201,25021520,1002
|
CS201,C,26021519,1002
|
||||||
CS202,25021520,1002
|
CS101,D,26021520,1001
|
||||||
EE101,25031519,1004
|
CS201,D,26021520,1002
|
||||||
EE102,25031519,1004
|
EE101,B,26031519,1004
|
||||||
CS101,26021519,1001
|
GEN101,A,25021519|25021520|25031519,1002
|
||||||
CS102,26021519,1001
|
GEN102,A,25021519|25021520|25031519,1006
|
||||||
CS201,26021519,1002
|
GEN101,B,26021519|26021520|26031519,1002
|
||||||
CS202,26021519,1002
|
GEN102,B,26021519|26021520|26031519,1006
|
||||||
CS101,26021520,1001
|
|
||||||
CS102,26021520,1001
|
|
||||||
CS201,26021520,1002
|
|
||||||
CS202,26021520,1002
|
|
||||||
EE101,26031519,1004
|
|
||||||
EE102,26031519,1004
|
|
||||||
GEN101,25021519|25021520|25031519,1002
|
|
||||||
GEN102,25021519|25021520|25031519,1006
|
|
||||||
GEN101,26021519|26021520|26031519,1002
|
|
||||||
GEN102,26021519|26021520|26031519,1006
|
|
||||||
|
|||||||
|
@@ -1,9 +1,6 @@
|
|||||||
course_id,course_name,course_type,credit_hours,is_general
|
course_id,course_name,credit_hours,lectures_per_week,labs_per_week,is_general,lecture_room_id,lab_room_id
|
||||||
CS101,Programming Fundamentals,theory,3,no
|
CS101,Programming Fundamentals,4,2,1,no,A-101,A-L01
|
||||||
CS102,Programming Fundamentals Lab,lab,1,no
|
CS201,Data Structures,4,2,1,no,A-102,A-L01
|
||||||
CS201,Data Structures,theory,3,no
|
EE101,Basic Electronics,4,2,1,no,A-201,A-L02
|
||||||
CS202,Data Structures Lab,lab,1,no
|
GEN101,Functional English,3,2,0,yes,A-103,
|
||||||
EE101,Basic Electronics,theory,3,no
|
GEN102,Islamic Studies,2,1,0,yes,A-103,
|
||||||
EE102,Basic Electronics Lab,lab,1,no
|
|
||||||
GEN101,Functional English,theory,3,yes
|
|
||||||
GEN102,Islamic Studies,theory,2,yes
|
|
||||||
|
|||||||
|
@@ -69,77 +69,91 @@ def main():
|
|||||||
|
|
||||||
all_batches = batches_25 + batches_26
|
all_batches = batches_25 + batches_26
|
||||||
|
|
||||||
# ---- instructors (4-digit employee codes) ----
|
|
||||||
instructors = [
|
|
||||||
["1001", "Dr. Ahsan Raza", "CS"],
|
|
||||||
["1002", "Ms. Sana Tariq", "CS"],
|
|
||||||
["1003", "Mr. Bilal Khan", "CS"],
|
|
||||||
["1004", "Dr. Farah Iqbal", "EE"],
|
|
||||||
["1005", "Mr. Usman Ali", "EE"],
|
|
||||||
["1006", "Ms. Hira Shah", "CS"],
|
|
||||||
]
|
|
||||||
write_csv(
|
|
||||||
"instructor/instructors.csv",
|
|
||||||
["instructor_code", "instructor_name", "department"],
|
|
||||||
instructors,
|
|
||||||
)
|
|
||||||
|
|
||||||
# ---- courses ----
|
# ---- courses ----
|
||||||
# is_general courses are shared across all batches of the same session
|
# One row per subject: lectures/labs per week are counted separately so
|
||||||
# (so they can be scheduled together on Tuesday with no clash).
|
# a single course can carry both, and each course names its preferred
|
||||||
|
# lecture/lab room (the scheduler falls back to another same-type room
|
||||||
|
# only if the preferred one is busy).
|
||||||
|
# is_general courses are shared across every batch of the same session
|
||||||
|
# (so they can be scheduled together on Tuesday first, with no clash).
|
||||||
courses = [
|
courses = [
|
||||||
["CS101", "Programming Fundamentals", "theory", 3, "no"],
|
# course_id, course_name, credit_hours, lectures_per_week, labs_per_week, is_general, lecture_room_id, lab_room_id
|
||||||
["CS102", "Programming Fundamentals Lab", "lab", 1, "no"],
|
["CS101", "Programming Fundamentals", 4, 2, 1, "no", "A-101", "A-L01"],
|
||||||
["CS201", "Data Structures", "theory", 3, "no"],
|
["CS201", "Data Structures", 4, 2, 1, "no", "A-102", "A-L01"],
|
||||||
["CS202", "Data Structures Lab", "lab", 1, "no"],
|
["EE101", "Basic Electronics", 4, 2, 1, "no", "A-201", "A-L02"],
|
||||||
["EE101", "Basic Electronics", "theory", 3, "no"],
|
["GEN101", "Functional English", 3, 2, 0, "yes", "A-103", ""],
|
||||||
["EE102", "Basic Electronics Lab", "lab", 1, "no"],
|
["GEN102", "Islamic Studies", 2, 1, 0, "yes", "A-103", ""],
|
||||||
["GEN101", "Functional English", "theory", 3, "yes"],
|
|
||||||
["GEN102", "Islamic Studies", "theory", 2, "yes"],
|
|
||||||
]
|
]
|
||||||
write_csv(
|
write_csv(
|
||||||
"courses/courses.csv",
|
"courses/courses.csv",
|
||||||
["course_id", "course_name", "course_type", "credit_hours", "is_general"],
|
[
|
||||||
|
"course_id", "course_name", "credit_hours", "lectures_per_week",
|
||||||
|
"labs_per_week", "is_general", "lecture_room_id", "lab_room_id",
|
||||||
|
],
|
||||||
courses,
|
courses,
|
||||||
)
|
)
|
||||||
|
|
||||||
# ---- course offerings: which course is taught to which batch, by whom ----
|
# ---- course offerings: course + section (a section groups the batches
|
||||||
# General courses (GEN101/GEN102) are offered once per session (25/26)
|
# taking that section together) + who teaches it ----
|
||||||
# and shared across every batch in that session.
|
# A course can have many sections; here each CS/EE batch of a session is
|
||||||
|
# its own section (Section A/B/...), while general courses have a single
|
||||||
|
# section "ALL" grouping every batch of that session.
|
||||||
offerings = []
|
offerings = []
|
||||||
|
|
||||||
dept_theory_lab = {
|
dept_course = {"CS": ["CS101", "CS201"], "EE": ["EE101"]}
|
||||||
"CS": [("CS101", "CS102"), ("CS201", "CS202")],
|
|
||||||
"EE": [("EE101", "EE102")],
|
|
||||||
}
|
|
||||||
dept_instructors = {
|
dept_instructors = {
|
||||||
"CS": ["1001", "1002", "1003", "1006"],
|
"CS": ["1001", "1002", "1003", "1006"],
|
||||||
"EE": ["1004", "1005"],
|
"EE": ["1004", "1005"],
|
||||||
}
|
}
|
||||||
|
|
||||||
for batch_code, department, program, session in all_batches:
|
section_letters = ["A", "B", "C", "D"]
|
||||||
pairs = dept_theory_lab[department]
|
dept_section_counter = {"CS": 0, "EE": 0}
|
||||||
instr_pool = dept_instructors[department]
|
|
||||||
for i, (theory_id, lab_id) in enumerate(pairs):
|
|
||||||
instr = instr_pool[i % len(instr_pool)]
|
|
||||||
offerings.append([theory_id, batch_code, instr])
|
|
||||||
offerings.append([lab_id, batch_code, instr])
|
|
||||||
|
|
||||||
# general courses: one offering per (course, session), batch_group lists
|
for batch_code, department, program, session in all_batches:
|
||||||
# every batch code sharing that session, separated by "|"
|
section_id = section_letters[dept_section_counter[department] % len(section_letters)]
|
||||||
|
dept_section_counter[department] += 1
|
||||||
|
|
||||||
|
for i, course_id in enumerate(dept_course[department]):
|
||||||
|
instr_pool = dept_instructors[department]
|
||||||
|
instr = instr_pool[i % len(instr_pool)]
|
||||||
|
offerings.append([course_id, section_id, batch_code, instr])
|
||||||
|
|
||||||
|
# general courses: one offering per (course, session), section "ALL"
|
||||||
|
# grouping every batch code sharing that session, separated by "|"
|
||||||
for session, batch_group in (
|
for session, batch_group in (
|
||||||
("25", [b[0] for b in batches_25]),
|
("25", [b[0] for b in batches_25]),
|
||||||
("26", [b[0] for b in batches_26]),
|
("26", [b[0] for b in batches_26]),
|
||||||
):
|
):
|
||||||
offerings.append(["GEN101", "|".join(batch_group), "1002"])
|
offerings.append(["GEN101", "ALL", "|".join(batch_group), "1002"])
|
||||||
offerings.append(["GEN102", "|".join(batch_group), "1006"])
|
offerings.append(["GEN102", "ALL", "|".join(batch_group), "1006"])
|
||||||
|
|
||||||
write_csv(
|
write_csv(
|
||||||
"courses/course_offering.csv",
|
"courses/course_offering.csv",
|
||||||
["course_id", "batch_code", "instructor_code"],
|
["course_id", "section_id", "batch_codes", "instructor_code"],
|
||||||
offerings,
|
offerings,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# ---- instructors (4-digit employee codes) ----
|
||||||
|
# Derived from the offerings above so it can never drift: one row per
|
||||||
|
# unique (instructor, course, section) teaching assignment.
|
||||||
|
instructor_names = {
|
||||||
|
"1001": "Dr. Ahsan Raza",
|
||||||
|
"1002": "Ms. Sana Tariq",
|
||||||
|
"1003": "Mr. Bilal Khan",
|
||||||
|
"1004": "Dr. Farah Iqbal",
|
||||||
|
"1005": "Mr. Usman Ali",
|
||||||
|
"1006": "Ms. Hira Shah",
|
||||||
|
}
|
||||||
|
instructors = []
|
||||||
|
for course_id, section_id, batch_codes, instr_code in offerings:
|
||||||
|
instructors.append([instr_code, instructor_names[instr_code], course_id, section_id])
|
||||||
|
|
||||||
|
write_csv(
|
||||||
|
"instructor/instructors.csv",
|
||||||
|
["instructor_code", "instructor_name", "course_id", "section_id"],
|
||||||
|
instructors,
|
||||||
|
)
|
||||||
|
|
||||||
# ---- students (roll_no = batch_code-seq) ----
|
# ---- students (roll_no = batch_code-seq) ----
|
||||||
students = []
|
students = []
|
||||||
for batch_code, department, program, session in all_batches:
|
for batch_code, department, program, session in all_batches:
|
||||||
|
|||||||
@@ -1,7 +1,15 @@
|
|||||||
instructor_code,instructor_name,department
|
instructor_code,instructor_name,course_id,section_id
|
||||||
1001,Dr. Ahsan Raza,CS
|
1001,Dr. Ahsan Raza,CS101,A
|
||||||
1002,Ms. Sana Tariq,CS
|
1002,Ms. Sana Tariq,CS201,A
|
||||||
1003,Mr. Bilal Khan,CS
|
1001,Dr. Ahsan Raza,CS101,B
|
||||||
1004,Dr. Farah Iqbal,EE
|
1002,Ms. Sana Tariq,CS201,B
|
||||||
1005,Mr. Usman Ali,EE
|
1004,Dr. Farah Iqbal,EE101,A
|
||||||
1006,Ms. Hira Shah,CS
|
1001,Dr. Ahsan Raza,CS101,C
|
||||||
|
1002,Ms. Sana Tariq,CS201,C
|
||||||
|
1001,Dr. Ahsan Raza,CS101,D
|
||||||
|
1002,Ms. Sana Tariq,CS201,D
|
||||||
|
1004,Dr. Farah Iqbal,EE101,B
|
||||||
|
1002,Ms. Sana Tariq,GEN101,ALL
|
||||||
|
1006,Ms. Hira Shah,GEN102,ALL
|
||||||
|
1002,Ms. Sana Tariq,GEN101,ALL
|
||||||
|
1006,Ms. Hira Shah,GEN102,ALL
|
||||||
|
|||||||
|
@@ -1,4 +1,4 @@
|
|||||||
Timetable generation log - started 2026-09-09T17:07:54.250382
|
Timetable generation log - started 2026-09-09T17:48:16.013663
|
||||||
================================================================================
|
================================================================================
|
||||||
[17:07:54] INFO: Running conflict checks (student/batch and teacher)...
|
[17:48:16] INFO: Running conflict checks (student/batch and teacher)...
|
||||||
[17:07:54] INFO: No conflicts found — schedule is clash-free for students and teachers.
|
[17:48:16] INFO: No conflicts found — schedule is clash-free for students and teachers.
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -37,7 +37,7 @@ def generate_batch_wise_templates(sessions, data, output_dir):
|
|||||||
lines = []
|
lines = []
|
||||||
for s in entries:
|
for s in entries:
|
||||||
tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "")
|
tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "")
|
||||||
lines.append(f'{s["course_name"]}{tag}\n{s["instructor_name"]}\n{s["room_id"]}')
|
lines.append(f'{s["course_name"]}{tag} [Sec {s["section_id"]}]\n{s["instructor_name"]}\n{s["room_id"]}')
|
||||||
return "\n---\n".join(lines)
|
return "\n---\n".join(lines)
|
||||||
|
|
||||||
first_by_day_slot = {k: v[0] for k, v in by_day_slot.items()}
|
first_by_day_slot = {k: v[0] for k, v in by_day_slot.items()}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ def generate_department_wise_templates(sessions, data, output_dir):
|
|||||||
for s in entries:
|
for s in entries:
|
||||||
tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "")
|
tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "")
|
||||||
batches = "+".join(s["batch_codes"])
|
batches = "+".join(s["batch_codes"])
|
||||||
lines.append(f'{s["course_name"]}{tag} [{batches}]\n{s["instructor_name"]} @ {s["room_id"]}')
|
lines.append(f'{s["course_name"]}{tag} [Sec {s["section_id"]}] [{batches}]\n{s["instructor_name"]} @ {s["room_id"]}')
|
||||||
return "\n---\n".join(lines)
|
return "\n---\n".join(lines)
|
||||||
|
|
||||||
# for fill-color purposes just use the first session in each cell
|
# for fill-color purposes just use the first session in each cell
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ def generate_room_wise_templates(sessions, data, output_dir):
|
|||||||
for s in entries:
|
for s in entries:
|
||||||
tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "")
|
tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "")
|
||||||
batches = "+".join(s["batch_codes"])
|
batches = "+".join(s["batch_codes"])
|
||||||
lines.append(f'{s["course_name"]}{tag}\n{batches}\n{s["instructor_name"]}')
|
lines.append(f'{s["course_name"]}{tag} [Sec {s["section_id"]}]\n{batches}\n{s["instructor_name"]}')
|
||||||
return "\n---\n".join(lines)
|
return "\n---\n".join(lines)
|
||||||
|
|
||||||
first_by_day_slot = {k: v[0] for k, v in by_day_slot.items()}
|
first_by_day_slot = {k: v[0] for k, v in by_day_slot.items()}
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ def generate_student_wise_templates(sessions, data, output_dir):
|
|||||||
for (day, slot_index), entries in raw_by_day_slot.items():
|
for (day, slot_index), entries in raw_by_day_slot.items():
|
||||||
if len(entries) > 1:
|
if len(entries) > 1:
|
||||||
course_list = ", ".join(
|
course_list = ", ".join(
|
||||||
f'{e["course_id"]} ({e["course_type"]}) with {e["instructor_name"]} in {e["room_id"]}'
|
f'{e["course_id"]} Sec {e["section_id"]} ({e["course_type"]}) with {e["instructor_name"]} in {e["room_id"]}'
|
||||||
for e in entries
|
for e in entries
|
||||||
)
|
)
|
||||||
log_error(
|
log_error(
|
||||||
@@ -52,7 +52,7 @@ def generate_student_wise_templates(sessions, data, output_dir):
|
|||||||
return ""
|
return ""
|
||||||
s = entries[0]
|
s = entries[0]
|
||||||
tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "")
|
tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "")
|
||||||
text = f'{s["course_name"]}{tag}\n{s["instructor_name"]}\n{s["room_id"]}'
|
text = f'{s["course_name"]}{tag} [Sec {s["section_id"]}]\n{s["instructor_name"]}\n{s["room_id"]}'
|
||||||
if len(entries) > 1:
|
if len(entries) > 1:
|
||||||
text = "CONFLICT!\n" + text
|
text = "CONFLICT!\n" + text
|
||||||
return text
|
return text
|
||||||
|
|||||||
@@ -14,8 +14,23 @@ from template_logger import log_error # noqa: E402
|
|||||||
|
|
||||||
def generate_teacher_wise_templates(sessions, data, output_dir):
|
def generate_teacher_wise_templates(sessions, data, output_dir):
|
||||||
written = []
|
written = []
|
||||||
for instructor in data["instructors"]:
|
|
||||||
code = instructor["instructor_code"]
|
# instructors.csv has one row per (instructor, course, section) teaching
|
||||||
|
# assignment, so the same instructor_code can repeat; render one sheet
|
||||||
|
# per unique instructor, listing all of their assignments in the subtitle.
|
||||||
|
seen_codes = []
|
||||||
|
assignments_by_code = {}
|
||||||
|
name_by_code = {}
|
||||||
|
for row in data["instructors"]:
|
||||||
|
code = row["instructor_code"]
|
||||||
|
if code not in assignments_by_code:
|
||||||
|
seen_codes.append(code)
|
||||||
|
assignments_by_code[code] = []
|
||||||
|
name_by_code[code] = row["instructor_name"]
|
||||||
|
assignments_by_code[code].append(f'{row["course_id"]} (Sec {row["section_id"]})')
|
||||||
|
|
||||||
|
for code in seen_codes:
|
||||||
|
instructor_name = name_by_code[code]
|
||||||
instr_sessions = [s for s in sessions if s["instructor_code"] == code]
|
instr_sessions = [s for s in sessions if s["instructor_code"] == code]
|
||||||
|
|
||||||
# A teacher can only be in one place at a time: each (day, slot)
|
# A teacher can only be in one place at a time: each (day, slot)
|
||||||
@@ -35,7 +50,7 @@ def generate_teacher_wise_templates(sessions, data, output_dir):
|
|||||||
for e in entries
|
for e in entries
|
||||||
)
|
)
|
||||||
log_error(
|
log_error(
|
||||||
f"Teacher conflict: {instructor['instructor_name']} ({code}) is double-booked on "
|
f"Teacher conflict: {instructor_name} ({code}) is double-booked on "
|
||||||
f"{day} slot {slot_index}: {course_list}"
|
f"{day} slot {slot_index}: {course_list}"
|
||||||
)
|
)
|
||||||
by_day_slot[(day, slot_index)] = entries[0]
|
by_day_slot[(day, slot_index)] = entries[0]
|
||||||
@@ -47,17 +62,19 @@ def generate_teacher_wise_templates(sessions, data, output_dir):
|
|||||||
s = entries[0]
|
s = entries[0]
|
||||||
tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "")
|
tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "")
|
||||||
batches = "+".join(s["batch_codes"])
|
batches = "+".join(s["batch_codes"])
|
||||||
text = f'{s["course_name"]}{tag}\n{batches}\n{s["room_id"]}'
|
text = f'{s["course_name"]}{tag} [Sec {s["section_id"]}]\n{batches}\n{s["room_id"]}'
|
||||||
if len(entries) > 1:
|
if len(entries) > 1:
|
||||||
text = "CONFLICT!\n" + text
|
text = "CONFLICT!\n" + text
|
||||||
return text
|
return text
|
||||||
|
|
||||||
|
subtitle = "Teaches: " + ", ".join(assignments_by_code[code])
|
||||||
|
|
||||||
wb = new_workbook()
|
wb = new_workbook()
|
||||||
ws = wb.active
|
ws = wb.active
|
||||||
build_grid_sheet(
|
build_grid_sheet(
|
||||||
ws,
|
ws,
|
||||||
title=f'{instructor["instructor_name"]} ({code})',
|
title=f'{instructor_name} ({code})',
|
||||||
subtitle=f'Department: {instructor["department"]}',
|
subtitle=subtitle,
|
||||||
time_slots=GRID_TIME_SLOTS,
|
time_slots=GRID_TIME_SLOTS,
|
||||||
sessions_by_day_slot=by_day_slot,
|
sessions_by_day_slot=by_day_slot,
|
||||||
cell_text_fn=cell_text,
|
cell_text_fn=cell_text,
|
||||||
|
|||||||
Reference in New Issue
Block a user