diff --git a/app/template_generator.py b/app/template_generator.py index 5b005c6..1f52ed2 100644 --- a/app/template_generator.py +++ b/app/template_generator.py @@ -25,6 +25,7 @@ from template_controller import build_schedule # noqa: E402 from conflict_checker import check_all_conflicts # noqa: E402 from conflict_checker.student_conflict_checker import format_student_conflicts # noqa: E402 from conflict_checker.teacher_conflict_checker import format_teacher_conflicts # noqa: E402 +from template_logger import reset_log, log_error, log_info # noqa: E402 from batch_wise_template import generate_batch_wise_templates # noqa: E402 from department_wise_template import generate_department_wise_templates # noqa: E402 @@ -36,6 +37,8 @@ RESULT_DIR = os.path.join(ROOT_DIR, "result", "templates") def main(): + reset_log() + data_csv_marker = os.path.join(ROOT_DIR, "data", "dummy", "blocks", "rooms.csv") if not os.path.exists(data_csv_marker): print("Dummy data not found — generating it first...") @@ -48,19 +51,19 @@ def main(): data, sessions = build_schedule() print(f"Scheduled {len(sessions)} sessions.\n") - print("Running conflict checks (student/batch and teacher)...") + log_info("Running conflict checks (student/batch and teacher)...") report = check_all_conflicts(sessions) if report["has_conflicts"]: for line in format_student_conflicts(report["student_conflicts"]): - print(f" STUDENT CONFLICT: {line}") + log_error(f"STUDENT CONFLICT: {line}") for line in format_teacher_conflicts(report["teacher_conflicts"]): - print(f" TEACHER CONFLICT: {line}") - print( + log_error(f"TEACHER CONFLICT: {line}") + log_error( f"Found {len(report['student_conflicts'])} student conflict(s) and " - f"{len(report['teacher_conflicts'])} teacher conflict(s).\n" + f"{len(report['teacher_conflicts'])} teacher conflict(s)." ) else: - print("No conflicts found — schedule is clash-free for students and teachers.\n") + log_info("No conflicts found — schedule is clash-free for students and teachers.") generators = [ ("batch-wise", generate_batch_wise_templates), diff --git a/app/template_logger.py b/app/template_logger.py new file mode 100644 index 0000000..4edbc76 --- /dev/null +++ b/app/template_logger.py @@ -0,0 +1,48 @@ +""" +Simple run-scoped logger for the template generator. + +The log file under logs/ is erased and recreated at the start of every run +(see reset_log()) so it only ever reflects the most recent generation. +""" + +import datetime +import os + +ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +LOGS_DIR = os.path.join(ROOT_DIR, "logs") +LOG_FILE = os.path.join(LOGS_DIR, "template_generator.log") + +_log_path = None + + +def reset_log(): + """Erases any previous log and starts a fresh one for this run.""" + global _log_path + os.makedirs(LOGS_DIR, exist_ok=True) + _log_path = LOG_FILE + with open(_log_path, "w", encoding="utf-8") as f: + f.write(f"Timetable generation log - started {datetime.datetime.now().isoformat()}\n") + f.write("=" * 80 + "\n") + return _log_path + + +def log_error(message): + """Appends an ERROR line to the current run's log file and prints it.""" + _write("ERROR", message) + + +def log_warning(message): + _write("WARNING", message) + + +def log_info(message): + _write("INFO", message) + + +def _write(level, message): + if _log_path is None: + reset_log() + line = f"[{datetime.datetime.now().strftime('%H:%M:%S')}] {level}: {message}" + with open(_log_path, "a", encoding="utf-8") as f: + f.write(line + "\n") + print(line) diff --git a/logs/template_generator.log b/logs/template_generator.log new file mode 100644 index 0000000..c0eea0f --- /dev/null +++ b/logs/template_generator.log @@ -0,0 +1,4 @@ +Timetable generation log - started 2026-09-09T17:06:29.991568 +================================================================================ +[17:06:29] INFO: Running conflict checks (student/batch and teacher)... +[17:06:29] INFO: No conflicts found — schedule is clash-free for students and teachers. diff --git a/result/templates/batch-wise/25021519.xlsx b/result/templates/batch-wise/25021519.xlsx index c0c45fa..372c827 100644 Binary files a/result/templates/batch-wise/25021519.xlsx and b/result/templates/batch-wise/25021519.xlsx differ diff --git a/result/templates/batch-wise/25021520.xlsx b/result/templates/batch-wise/25021520.xlsx index 733bcd6..df7a2e8 100644 Binary files a/result/templates/batch-wise/25021520.xlsx and b/result/templates/batch-wise/25021520.xlsx differ diff --git a/result/templates/batch-wise/25031519.xlsx b/result/templates/batch-wise/25031519.xlsx index f208379..146c4d5 100644 Binary files a/result/templates/batch-wise/25031519.xlsx and b/result/templates/batch-wise/25031519.xlsx differ diff --git a/result/templates/batch-wise/26021519.xlsx b/result/templates/batch-wise/26021519.xlsx index 2a42823..bbf873c 100644 Binary files a/result/templates/batch-wise/26021519.xlsx and b/result/templates/batch-wise/26021519.xlsx differ diff --git a/result/templates/batch-wise/26021520.xlsx b/result/templates/batch-wise/26021520.xlsx index b8c5f69..31791ae 100644 Binary files a/result/templates/batch-wise/26021520.xlsx and b/result/templates/batch-wise/26021520.xlsx differ diff --git a/result/templates/batch-wise/26031519.xlsx b/result/templates/batch-wise/26031519.xlsx index 5b7bca2..07c9ef5 100644 Binary files a/result/templates/batch-wise/26031519.xlsx and b/result/templates/batch-wise/26031519.xlsx differ diff --git a/result/templates/department-wise/CS.xlsx b/result/templates/department-wise/CS.xlsx index a5d9e65..1675447 100644 Binary files a/result/templates/department-wise/CS.xlsx and b/result/templates/department-wise/CS.xlsx differ diff --git a/result/templates/department-wise/EE.xlsx b/result/templates/department-wise/EE.xlsx index 016287b..38c7dd0 100644 Binary files a/result/templates/department-wise/EE.xlsx and b/result/templates/department-wise/EE.xlsx differ diff --git a/result/templates/room-wise/A-101.xlsx b/result/templates/room-wise/A-101.xlsx index 9fab134..11093e0 100644 Binary files a/result/templates/room-wise/A-101.xlsx and b/result/templates/room-wise/A-101.xlsx differ diff --git a/result/templates/room-wise/A-102.xlsx b/result/templates/room-wise/A-102.xlsx index c6c4dda..7ec04cd 100644 Binary files a/result/templates/room-wise/A-102.xlsx and b/result/templates/room-wise/A-102.xlsx differ diff --git a/result/templates/room-wise/A-103.xlsx b/result/templates/room-wise/A-103.xlsx index 0289c41..bf81728 100644 Binary files a/result/templates/room-wise/A-103.xlsx and b/result/templates/room-wise/A-103.xlsx differ diff --git a/result/templates/room-wise/A-201.xlsx b/result/templates/room-wise/A-201.xlsx index a2dbce8..aa13536 100644 Binary files a/result/templates/room-wise/A-201.xlsx and b/result/templates/room-wise/A-201.xlsx differ diff --git a/result/templates/room-wise/A-L01.xlsx b/result/templates/room-wise/A-L01.xlsx index 249958c..91ec00d 100644 Binary files a/result/templates/room-wise/A-L01.xlsx and b/result/templates/room-wise/A-L01.xlsx differ diff --git a/result/templates/room-wise/A-L02.xlsx b/result/templates/room-wise/A-L02.xlsx index 83626f4..77297e0 100644 Binary files a/result/templates/room-wise/A-L02.xlsx and b/result/templates/room-wise/A-L02.xlsx differ diff --git a/result/templates/student-wise/25021519-001.xlsx b/result/templates/student-wise/25021519-001.xlsx index 96f2c18..283ca48 100644 Binary files a/result/templates/student-wise/25021519-001.xlsx and b/result/templates/student-wise/25021519-001.xlsx differ diff --git a/result/templates/student-wise/25021519-002.xlsx b/result/templates/student-wise/25021519-002.xlsx index 893be02..21e83c0 100644 Binary files a/result/templates/student-wise/25021519-002.xlsx and b/result/templates/student-wise/25021519-002.xlsx differ diff --git a/result/templates/student-wise/25021519-003.xlsx b/result/templates/student-wise/25021519-003.xlsx index 7e4e585..b912912 100644 Binary files a/result/templates/student-wise/25021519-003.xlsx and b/result/templates/student-wise/25021519-003.xlsx differ diff --git a/result/templates/student-wise/25021519-004.xlsx b/result/templates/student-wise/25021519-004.xlsx index c803a24..a72fc6f 100644 Binary files a/result/templates/student-wise/25021519-004.xlsx and b/result/templates/student-wise/25021519-004.xlsx differ diff --git a/result/templates/student-wise/25021519-005.xlsx b/result/templates/student-wise/25021519-005.xlsx index 1f54fc6..d91154e 100644 Binary files a/result/templates/student-wise/25021519-005.xlsx and b/result/templates/student-wise/25021519-005.xlsx differ diff --git a/result/templates/student-wise/25021520-001.xlsx b/result/templates/student-wise/25021520-001.xlsx index 5e20a9f..44d6a24 100644 Binary files a/result/templates/student-wise/25021520-001.xlsx and b/result/templates/student-wise/25021520-001.xlsx differ diff --git a/result/templates/student-wise/25021520-002.xlsx b/result/templates/student-wise/25021520-002.xlsx index c97387d..8f41b22 100644 Binary files a/result/templates/student-wise/25021520-002.xlsx and b/result/templates/student-wise/25021520-002.xlsx differ diff --git a/result/templates/student-wise/25021520-003.xlsx b/result/templates/student-wise/25021520-003.xlsx index a8eac05..09e775d 100644 Binary files a/result/templates/student-wise/25021520-003.xlsx and b/result/templates/student-wise/25021520-003.xlsx differ diff --git a/result/templates/student-wise/25021520-004.xlsx b/result/templates/student-wise/25021520-004.xlsx index cc1ea7a..60190a6 100644 Binary files a/result/templates/student-wise/25021520-004.xlsx and b/result/templates/student-wise/25021520-004.xlsx differ diff --git a/result/templates/student-wise/25021520-005.xlsx b/result/templates/student-wise/25021520-005.xlsx index dda79bf..dbac665 100644 Binary files a/result/templates/student-wise/25021520-005.xlsx and b/result/templates/student-wise/25021520-005.xlsx differ diff --git a/result/templates/student-wise/25031519-001.xlsx b/result/templates/student-wise/25031519-001.xlsx index 4c3b9d2..04d83dd 100644 Binary files a/result/templates/student-wise/25031519-001.xlsx and b/result/templates/student-wise/25031519-001.xlsx differ diff --git a/result/templates/student-wise/25031519-002.xlsx b/result/templates/student-wise/25031519-002.xlsx index 1b972a9..7fd3b22 100644 Binary files a/result/templates/student-wise/25031519-002.xlsx and b/result/templates/student-wise/25031519-002.xlsx differ diff --git a/result/templates/student-wise/25031519-003.xlsx b/result/templates/student-wise/25031519-003.xlsx index 11f5ee9..bbc91c4 100644 Binary files a/result/templates/student-wise/25031519-003.xlsx and b/result/templates/student-wise/25031519-003.xlsx differ diff --git a/result/templates/student-wise/25031519-004.xlsx b/result/templates/student-wise/25031519-004.xlsx index 139e5b3..5904882 100644 Binary files a/result/templates/student-wise/25031519-004.xlsx and b/result/templates/student-wise/25031519-004.xlsx differ diff --git a/result/templates/student-wise/25031519-005.xlsx b/result/templates/student-wise/25031519-005.xlsx index 3ef170a..bbf2543 100644 Binary files a/result/templates/student-wise/25031519-005.xlsx and b/result/templates/student-wise/25031519-005.xlsx differ diff --git a/result/templates/student-wise/26021519-001.xlsx b/result/templates/student-wise/26021519-001.xlsx index 2623176..657c1c5 100644 Binary files a/result/templates/student-wise/26021519-001.xlsx and b/result/templates/student-wise/26021519-001.xlsx differ diff --git a/result/templates/student-wise/26021519-002.xlsx b/result/templates/student-wise/26021519-002.xlsx index 04ec9e8..3ed3271 100644 Binary files a/result/templates/student-wise/26021519-002.xlsx and b/result/templates/student-wise/26021519-002.xlsx differ diff --git a/result/templates/student-wise/26021519-003.xlsx b/result/templates/student-wise/26021519-003.xlsx index d4e3e97..c01752b 100644 Binary files a/result/templates/student-wise/26021519-003.xlsx and b/result/templates/student-wise/26021519-003.xlsx differ diff --git a/result/templates/student-wise/26021519-004.xlsx b/result/templates/student-wise/26021519-004.xlsx index 1369ccf..921c1f9 100644 Binary files a/result/templates/student-wise/26021519-004.xlsx and b/result/templates/student-wise/26021519-004.xlsx differ diff --git a/result/templates/student-wise/26021519-005.xlsx b/result/templates/student-wise/26021519-005.xlsx index 0dc3de6..497af01 100644 Binary files a/result/templates/student-wise/26021519-005.xlsx and b/result/templates/student-wise/26021519-005.xlsx differ diff --git a/result/templates/student-wise/26021520-001.xlsx b/result/templates/student-wise/26021520-001.xlsx index 605adcf..3a2cc21 100644 Binary files a/result/templates/student-wise/26021520-001.xlsx and b/result/templates/student-wise/26021520-001.xlsx differ diff --git a/result/templates/student-wise/26021520-002.xlsx b/result/templates/student-wise/26021520-002.xlsx index 9364a3c..d67710a 100644 Binary files a/result/templates/student-wise/26021520-002.xlsx and b/result/templates/student-wise/26021520-002.xlsx differ diff --git a/result/templates/student-wise/26021520-003.xlsx b/result/templates/student-wise/26021520-003.xlsx index 17a7bb8..07cbc97 100644 Binary files a/result/templates/student-wise/26021520-003.xlsx and b/result/templates/student-wise/26021520-003.xlsx differ diff --git a/result/templates/student-wise/26021520-004.xlsx b/result/templates/student-wise/26021520-004.xlsx index 1e6fb56..db84814 100644 Binary files a/result/templates/student-wise/26021520-004.xlsx and b/result/templates/student-wise/26021520-004.xlsx differ diff --git a/result/templates/student-wise/26021520-005.xlsx b/result/templates/student-wise/26021520-005.xlsx index 6d06cac..cbd81f1 100644 Binary files a/result/templates/student-wise/26021520-005.xlsx and b/result/templates/student-wise/26021520-005.xlsx differ diff --git a/result/templates/student-wise/26031519-001.xlsx b/result/templates/student-wise/26031519-001.xlsx index ec5cf0f..9335fcd 100644 Binary files a/result/templates/student-wise/26031519-001.xlsx and b/result/templates/student-wise/26031519-001.xlsx differ diff --git a/result/templates/student-wise/26031519-002.xlsx b/result/templates/student-wise/26031519-002.xlsx index d72aab3..9b0f9e2 100644 Binary files a/result/templates/student-wise/26031519-002.xlsx and b/result/templates/student-wise/26031519-002.xlsx differ diff --git a/result/templates/student-wise/26031519-003.xlsx b/result/templates/student-wise/26031519-003.xlsx index 3f20c01..3c8b97a 100644 Binary files a/result/templates/student-wise/26031519-003.xlsx and b/result/templates/student-wise/26031519-003.xlsx differ diff --git a/result/templates/student-wise/26031519-004.xlsx b/result/templates/student-wise/26031519-004.xlsx index ccd07b6..68a7984 100644 Binary files a/result/templates/student-wise/26031519-004.xlsx and b/result/templates/student-wise/26031519-004.xlsx differ diff --git a/result/templates/student-wise/26031519-005.xlsx b/result/templates/student-wise/26031519-005.xlsx index e64dc11..e467421 100644 Binary files a/result/templates/student-wise/26031519-005.xlsx and b/result/templates/student-wise/26031519-005.xlsx differ diff --git a/result/templates/teacher-wise/1001.xlsx b/result/templates/teacher-wise/1001.xlsx index a829e6e..8956d63 100644 Binary files a/result/templates/teacher-wise/1001.xlsx and b/result/templates/teacher-wise/1001.xlsx differ diff --git a/result/templates/teacher-wise/1002.xlsx b/result/templates/teacher-wise/1002.xlsx index 4228c52..4cc6a16 100644 Binary files a/result/templates/teacher-wise/1002.xlsx and b/result/templates/teacher-wise/1002.xlsx differ diff --git a/result/templates/teacher-wise/1003.xlsx b/result/templates/teacher-wise/1003.xlsx index 37fca81..fa91413 100644 Binary files a/result/templates/teacher-wise/1003.xlsx and b/result/templates/teacher-wise/1003.xlsx differ diff --git a/result/templates/teacher-wise/1004.xlsx b/result/templates/teacher-wise/1004.xlsx index 53eb778..7222ab0 100644 Binary files a/result/templates/teacher-wise/1004.xlsx and b/result/templates/teacher-wise/1004.xlsx differ diff --git a/result/templates/teacher-wise/1005.xlsx b/result/templates/teacher-wise/1005.xlsx index 2fa593d..a357193 100644 Binary files a/result/templates/teacher-wise/1005.xlsx and b/result/templates/teacher-wise/1005.xlsx differ diff --git a/result/templates/teacher-wise/1006.xlsx b/result/templates/teacher-wise/1006.xlsx index 77f3498..eec240c 100644 Binary files a/result/templates/teacher-wise/1006.xlsx and b/result/templates/teacher-wise/1006.xlsx differ diff --git a/templates/batch-wise/batch_wise_template.py b/templates/batch-wise/batch_wise_template.py index f887bcd..aaa617c 100644 --- a/templates/batch-wise/batch_wise_template.py +++ b/templates/batch-wise/batch_wise_template.py @@ -21,17 +21,26 @@ def generate_batch_wise_templates(sessions, data, output_dir): batch_code = batch["batch_code"] batch_sessions = _sessions_for_batch(sessions, batch_code) + # A batch-wise view can legitimately show more than one lecture in + # the same slot (e.g. sections of the batch split across rooms), so + # every session landing on a cell is kept and rendered — none are + # dropped or overwritten. by_day_slot = {} for s in batch_sessions: for slot_index in s["slot_indices"]: - by_day_slot[(s["day"], slot_index)] = s + by_day_slot.setdefault((s["day"], slot_index), []).append(s) def cell_text(day, slot_index, by_day_slot=by_day_slot): - s = by_day_slot.get((day, slot_index)) - if not s: + entries = by_day_slot.get((day, slot_index)) + if not entries: return "" - tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "") - return f'{s["course_name"]}{tag}\n{s["instructor_name"]}\n{s["room_id"]}' + lines = [] + for s in entries: + 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"]}') + return "\n---\n".join(lines) + + first_by_day_slot = {k: v[0] for k, v in by_day_slot.items()} wb = new_workbook() ws = wb.active @@ -41,7 +50,7 @@ def generate_batch_wise_templates(sessions, data, output_dir): title=f"Batch {batch_code}", subtitle=subtitle, time_slots=GRID_TIME_SLOTS, - sessions_by_day_slot=by_day_slot, + sessions_by_day_slot=first_by_day_slot, cell_text_fn=cell_text, ) path = save_workbook(wb, output_dir, f"{batch_code}.xlsx") diff --git a/templates/room-wise/room_wise_template.py b/templates/room-wise/room_wise_template.py index 8d5832f..31c398b 100644 --- a/templates/room-wise/room_wise_template.py +++ b/templates/room-wise/room_wise_template.py @@ -17,18 +17,26 @@ def generate_room_wise_templates(sessions, data, output_dir): room_id = room["room_id"] room_sessions = [s for s in sessions if s["room_id"] == room_id] + # A room-wise view can legitimately show more than one lecture in + # the same slot (e.g. a large room split/shared), so every session + # landing on a cell is kept and rendered — none are dropped. by_day_slot = {} for s in room_sessions: for slot_index in s["slot_indices"]: - by_day_slot[(s["day"], slot_index)] = s + by_day_slot.setdefault((s["day"], slot_index), []).append(s) def cell_text(day, slot_index, by_day_slot=by_day_slot): - s = by_day_slot.get((day, slot_index)) - if not s: + entries = by_day_slot.get((day, slot_index)) + if not entries: return "" - tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "") - batches = "+".join(s["batch_codes"]) - return f'{s["course_name"]}{tag}\n{batches}\n{s["instructor_name"]}' + lines = [] + for s in entries: + tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "") + batches = "+".join(s["batch_codes"]) + lines.append(f'{s["course_name"]}{tag}\n{batches}\n{s["instructor_name"]}') + return "\n---\n".join(lines) + + first_by_day_slot = {k: v[0] for k, v in by_day_slot.items()} wb = new_workbook() ws = wb.active @@ -37,7 +45,7 @@ def generate_room_wise_templates(sessions, data, output_dir): title=f'{room["room_name"]} ({room_id})', subtitle=f'Block {room["block_id"]} - {room["room_type"].title()} - Capacity {room["capacity"]}', time_slots=GRID_TIME_SLOTS, - sessions_by_day_slot=by_day_slot, + sessions_by_day_slot=first_by_day_slot, cell_text_fn=cell_text, ) path = save_workbook(wb, output_dir, f"{room_id}.xlsx") diff --git a/templates/student-wise/student_wise_template.py b/templates/student-wise/student_wise_template.py index d07014d..07f9c0b 100644 --- a/templates/student-wise/student_wise_template.py +++ b/templates/student-wise/student_wise_template.py @@ -9,6 +9,7 @@ sys.path.insert(0, os.path.join(_ROOT, "controller")) from xlsx_grid_helper import build_grid_sheet, new_workbook, save_workbook # noqa: E402 from template_controller import TIME_SLOTS as GRID_TIME_SLOTS # noqa: E402 +from template_logger import log_error # noqa: E402 def _sessions_for_batch(sessions, batch_code): @@ -23,17 +24,38 @@ def generate_student_wise_templates(sessions, data, output_dir): batch = data["batches_by_code"].get(batch_code) student_sessions = _sessions_for_batch(sessions, batch_code) - by_day_slot = {} + # A student can only be in one place at a time: each (day, slot) + # must resolve to exactly one session. If the schedule places more + # than one, that's a real scheduling error - log it instead of + # silently picking/overwriting one. + raw_by_day_slot = {} for s in student_sessions: for slot_index in s["slot_indices"]: - by_day_slot[(s["day"], slot_index)] = s + raw_by_day_slot.setdefault((s["day"], slot_index), []).append(s) - def cell_text(day, slot_index, by_day_slot=by_day_slot): - s = by_day_slot.get((day, slot_index)) - if not s: + by_day_slot = {} + for (day, slot_index), entries in raw_by_day_slot.items(): + if len(entries) > 1: + course_list = ", ".join( + f'{e["course_id"]} ({e["course_type"]}) with {e["instructor_name"]} in {e["room_id"]}' + for e in entries + ) + log_error( + f"Student conflict: roll {roll_no} (batch {batch_code}) has " + f"{len(entries)} overlapping courses on {day} slot {slot_index}: {course_list}" + ) + by_day_slot[(day, slot_index)] = entries[0] + + def cell_text(day, slot_index, raw_by_day_slot=raw_by_day_slot): + entries = raw_by_day_slot.get((day, slot_index)) + if not entries: return "" + s = entries[0] tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "") - return f'{s["course_name"]}{tag}\n{s["instructor_name"]}\n{s["room_id"]}' + text = f'{s["course_name"]}{tag}\n{s["instructor_name"]}\n{s["room_id"]}' + if len(entries) > 1: + text = "CONFLICT!\n" + text + return text subtitle = f'{student["student_name"]} - Batch {batch_code}' if batch: diff --git a/templates/teacher-wise/teacher_wise_template.py b/templates/teacher-wise/teacher_wise_template.py index 46ae62f..76e6732 100644 --- a/templates/teacher-wise/teacher_wise_template.py +++ b/templates/teacher-wise/teacher_wise_template.py @@ -9,6 +9,7 @@ sys.path.insert(0, os.path.join(_ROOT, "controller")) from xlsx_grid_helper import build_grid_sheet, new_workbook, save_workbook # noqa: E402 from template_controller import TIME_SLOTS as GRID_TIME_SLOTS # noqa: E402 +from template_logger import log_error # noqa: E402 def generate_teacher_wise_templates(sessions, data, output_dir): @@ -17,18 +18,39 @@ def generate_teacher_wise_templates(sessions, data, output_dir): code = instructor["instructor_code"] instr_sessions = [s for s in sessions if s["instructor_code"] == code] - by_day_slot = {} + # A teacher can only be in one place at a time: each (day, slot) + # must resolve to exactly one session. If the schedule places more + # than one, that's a real scheduling error - log it instead of + # silently picking/overwriting one. + raw_by_day_slot = {} for s in instr_sessions: for slot_index in s["slot_indices"]: - by_day_slot[(s["day"], slot_index)] = s + raw_by_day_slot.setdefault((s["day"], slot_index), []).append(s) - def cell_text(day, slot_index, by_day_slot=by_day_slot): - s = by_day_slot.get((day, slot_index)) - if not s: + by_day_slot = {} + for (day, slot_index), entries in raw_by_day_slot.items(): + if len(entries) > 1: + course_list = ", ".join( + f'{e["course_id"]} ({e["course_type"]}) for batch(es) {"+".join(e["batch_codes"])} in {e["room_id"]}' + for e in entries + ) + log_error( + f"Teacher conflict: {instructor['instructor_name']} ({code}) is double-booked on " + f"{day} slot {slot_index}: {course_list}" + ) + by_day_slot[(day, slot_index)] = entries[0] + + def cell_text(day, slot_index, raw_by_day_slot=raw_by_day_slot): + entries = raw_by_day_slot.get((day, slot_index)) + if not entries: return "" + s = entries[0] tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "") batches = "+".join(s["batch_codes"]) - return f'{s["course_name"]}{tag}\n{batches}\n{s["room_id"]}' + text = f'{s["course_name"]}{tag}\n{batches}\n{s["room_id"]}' + if len(entries) > 1: + text = "CONFLICT!\n" + text + return text wb = new_workbook() ws = wb.active