252 lines
10 KiB
Python
252 lines
10 KiB
Python
"""
|
|
Generates a larger, deliberately harder dummy CSV dataset under data/dummy/
|
|
so the timetable template generator has something non-trivial to schedule
|
|
and render — more departments/batches/sections than the previous small
|
|
dataset, tighter room and instructor contention (fewer rooms/instructors
|
|
relative to demand), a second block, and mixed lecture durations (90 vs
|
|
120 minutes) so weaknesses in the scheduler surface as WARNINGs/conflicts
|
|
instead of everything trivially fitting.
|
|
|
|
Run once:
|
|
python data/dummy/generate_dummy_data.py
|
|
"""
|
|
|
|
import csv
|
|
import os
|
|
|
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
def write_csv(relative_path, header, rows):
|
|
path = os.path.join(BASE_DIR, relative_path)
|
|
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
with open(path, "w", newline="", encoding="utf-8") as f:
|
|
writer = csv.writer(f)
|
|
writer.writerow(header)
|
|
writer.writerows(rows)
|
|
print(f"wrote {len(rows)} rows -> {path}")
|
|
|
|
|
|
def main():
|
|
# ---- blocks: two blocks now, Block A (bigger) and Block B (smaller) ----
|
|
write_csv(
|
|
"blocks/blocks.csv",
|
|
["block_id", "block_name"],
|
|
[
|
|
["A", "Block A"],
|
|
["B", "Block B"],
|
|
],
|
|
)
|
|
|
|
# ---- rooms: tight enough to keep real contention, but with enough
|
|
# total capacity that a correct solver can always find a fit (room
|
|
# capacity/student-count is not a constraint here) ----
|
|
write_csv(
|
|
"blocks/rooms.csv",
|
|
["room_id", "room_name", "block_id", "room_type", "capacity"],
|
|
[
|
|
["A-101", "Room 101", "A", "classroom", 60],
|
|
["A-102", "Room 102", "A", "classroom", 60],
|
|
["A-103", "Room 103", "A", "classroom", 50],
|
|
["A-201", "Room 201", "A", "classroom", 60],
|
|
["A-202", "Room 202", "A", "classroom", 60],
|
|
["A-L01", "Computer Lab 1", "A", "lab", 35],
|
|
["A-L02", "Computer Lab 2", "A", "lab", 35],
|
|
["A-L03", "Computer Lab 3", "A", "lab", 35],
|
|
["B-101", "Room 101 (B)", "B", "classroom", 50],
|
|
["B-102", "Room 102 (B)", "B", "classroom", 50],
|
|
["B-L01", "Electronics Lab", "B", "lab", 30],
|
|
],
|
|
)
|
|
|
|
# ---- departments / programs / batches: 3 departments, 2 programs
|
|
# each, 2 sessions (25/26) -> 12 batches total (was 6) ----
|
|
department_programs = {
|
|
"CS": ["BSCS", "BSSE"],
|
|
"EE": ["BSEE", "BSCE"],
|
|
"BBA": ["BBA", "BSAF"],
|
|
}
|
|
program_codes = {
|
|
"BSCS": "021519", "BSSE": "021520",
|
|
"BSEE": "031519", "BSCE": "031520",
|
|
"BBA": "041519", "BSAF": "041520",
|
|
}
|
|
|
|
batches_25 = []
|
|
batches_26 = []
|
|
for department, programs in department_programs.items():
|
|
for program in programs:
|
|
code = program_codes[program]
|
|
batches_25.append([f"25{code}", department, program, "25"])
|
|
batches_26.append([f"26{code}", department, program, "26"])
|
|
|
|
write_csv(
|
|
"batch_codes/25/batches.csv",
|
|
["batch_code", "department", "program", "session"],
|
|
batches_25,
|
|
)
|
|
write_csv(
|
|
"batch_codes/26/batches.csv",
|
|
["batch_code", "department", "program", "session"],
|
|
batches_26,
|
|
)
|
|
|
|
all_batches = batches_25 + batches_26
|
|
|
|
# ---- courses ----
|
|
# course_id, course_name, credit_hours, lectures_per_week,
|
|
# labs_per_week, is_general, lecture_room_id, lab_room_id,
|
|
# lecture_duration_minutes
|
|
#
|
|
# Mixed lecture durations: most courses are 90-min x N/week, but a few
|
|
# are the special "2 hours once a week" case (lecture_duration_minutes
|
|
# 120, lectures_per_week 1). A course's lab, if it has one, is always
|
|
# exactly once a week (180 minutes) — never more. Deliberately more
|
|
# courses per department than the room/instructor pool can comfortably
|
|
# absorb without contention.
|
|
courses = [
|
|
# CS department courses — lab rooms spread across A-L01/A-L02/A-L03
|
|
# so no single lab room is asked for more sessions than exist
|
|
# weekly slots for it (5 days x 2 lab-slot-pairs = 10/week).
|
|
["CS101", "Programming Fundamentals", 4, 2, 1, "no", "A-101", "A-L01", 90],
|
|
["CS201", "Data Structures", 4, 2, 1, "no", "A-102", "A-L02", 90],
|
|
["CS301", "Operating Systems", 3, 1, 1, "no", "A-101", "A-L03", 120],
|
|
["CS302", "Database Systems", 4, 2, 1, "no", "A-102", "A-L01", 90],
|
|
# EE department courses
|
|
["EE101", "Basic Electronics", 4, 2, 1, "no", "A-201", "A-L02", 90],
|
|
["EE201", "Digital Logic Design", 4, 2, 1, "no", "B-101", "B-L01", 90],
|
|
["EE301", "Signals and Systems", 3, 1, 0, "no", "B-102", "", 120],
|
|
# BBA department courses
|
|
["BBA101", "Principles of Management", 3, 2, 0, "no", "B-101", "", 90],
|
|
["BBA201", "Financial Accounting", 3, 2, 0, "no", "B-102", "", 90],
|
|
["BBA301", "Marketing Management", 3, 1, 0, "no", "A-103", "", 120],
|
|
# General (shared) courses
|
|
["GEN101", "Functional English", 3, 2, 0, "yes", "A-103", "", 90],
|
|
["GEN102", "Islamic Studies", 2, 1, 0, "yes", "A-103", "", 90],
|
|
["GEN103", "Pakistan Studies", 2, 1, 0, "yes", "A-202", "", 120],
|
|
]
|
|
write_csv(
|
|
"courses/courses.csv",
|
|
[
|
|
"course_id", "course_name", "credit_hours", "lectures_per_week",
|
|
"labs_per_week", "is_general", "lecture_room_id", "lab_room_id",
|
|
"lecture_duration_minutes",
|
|
],
|
|
courses,
|
|
)
|
|
|
|
# ---- course offerings: course + section (batches in that section) +
|
|
# instructor ----
|
|
# Deliberately tight instructor pool reused across many sections/
|
|
# courses (some instructors will end up wanted in overlapping slots),
|
|
# forcing real contention in Phase 2/Phase 3.
|
|
dept_courses = {
|
|
"CS": ["CS101", "CS201", "CS301", "CS302"],
|
|
"EE": ["EE101", "EE201", "EE301"],
|
|
"BBA": ["BBA101", "BBA201", "BBA301"],
|
|
}
|
|
# One instructor per course within a department (so no instructor ever
|
|
# teaches two different courses to the same section/batch, which would
|
|
# double their weekly slot demand against that batch's narrow shared
|
|
# availability) — still a tight pool overall since each instructor
|
|
# teaches their course across every section of the department.
|
|
dept_instructors = {
|
|
"CS": ["1001", "1002", "1003", "1009"],
|
|
"EE": ["1004", "1005", "1010"],
|
|
"BBA": ["1007", "1008", "1011"],
|
|
}
|
|
# CS302 additionally alternates between two instructors across its
|
|
# sections (instead of one instructor owning all 4) since it has the
|
|
# heaviest per-section load (2 lectures + 2 labs/week) — one person
|
|
# covering every section of it would need more slot-0 room than a
|
|
# single instructor realistically has once other batches' commitments
|
|
# are accounted for.
|
|
course_instructor_rotation = {"CS302": ["1009", "1012"]}
|
|
instructor_names_extra = {"1012": "Ms. Rabia Nasir"}
|
|
|
|
offerings = []
|
|
section_letters = ["A", "B", "C", "D"]
|
|
dept_section_counter = {"CS": 0, "EE": 0, "BBA": 0}
|
|
|
|
for batch_code, department, program, session in all_batches:
|
|
section_index = dept_section_counter[department]
|
|
section_id = section_letters[section_index % len(section_letters)]
|
|
dept_section_counter[department] += 1
|
|
|
|
instr_pool = dept_instructors[department]
|
|
# One instructor per course, consistent across every section, so
|
|
# no instructor ever teaches two different courses to the same
|
|
# batch (which would silently double their contested slot-0
|
|
# demand against that one batch). The pool is still tight overall
|
|
# since each instructor teaches their course to every section of
|
|
# the department -> real instructor contention across sections.
|
|
for i, course_id in enumerate(dept_courses[department]):
|
|
if course_id in course_instructor_rotation:
|
|
rotation = course_instructor_rotation[course_id]
|
|
instr = rotation[section_index % len(rotation)]
|
|
else:
|
|
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
|
|
for session, batch_group in (
|
|
("25", [b[0] for b in batches_25]),
|
|
("26", [b[0] for b in batches_26]),
|
|
):
|
|
offerings.append(["GEN101", "ALL", "|".join(batch_group), "1002"])
|
|
offerings.append(["GEN102", "ALL", "|".join(batch_group), "1006"])
|
|
offerings.append(["GEN103", "ALL", "|".join(batch_group), "1006"])
|
|
|
|
write_csv(
|
|
"courses/course_offering.csv",
|
|
["course_id", "section_id", "batch_codes", "instructor_code"],
|
|
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",
|
|
"1007": "Dr. Nadia Farooq",
|
|
"1008": "Mr. Kamran Sheikh",
|
|
"1009": "Ms. Ayesha Malik",
|
|
"1010": "Mr. Imran Qureshi",
|
|
"1011": "Dr. Saima Aziz",
|
|
**instructor_names_extra,
|
|
}
|
|
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): 18 per batch (was 5) ----
|
|
students = []
|
|
for batch_code, department, program, session in all_batches:
|
|
for seq in range(1, 19):
|
|
roll_no = f"{batch_code}-{seq:03d}"
|
|
students.append([roll_no, batch_code, f"Student {roll_no}"])
|
|
|
|
write_csv(
|
|
"roll_no/students.csv",
|
|
["roll_no", "batch_code", "student_name"],
|
|
students,
|
|
)
|
|
|
|
print("\nDummy data generation complete.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|