"""Renders one timetable grid per student (same grid as their batch, labeled per student).""" import sys import os _ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "..") sys.path.insert(0, os.path.join(_ROOT, "app")) 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 DISPLAY_ROWS # noqa: E402 from template_logger import log_error # noqa: E402 def _sessions_for_batch(sessions, batch_code): return [s for s in sessions if batch_code in s["batch_codes"]] def generate_student_wise_templates(sessions, data, output_dir): written = [] for student in data["students"]: roll_no = student["roll_no"] batch_code = student["batch_code"] batch = data["batches_by_code"].get(batch_code) student_sessions = _sessions_for_batch(sessions, batch_code) # A student can only be in one place at a time: each (day, slot, # duration) 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"]: raw_by_day_slot.setdefault((s["day"], slot_index, s["duration_minutes"]), []).append(s) by_day_slot = {} for (day, slot_index, duration_minutes), entries in raw_by_day_slot.items(): if len(entries) > 1: course_list = ", ".join( f'{e["course_id"]} Sec {e["section_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, duration_minutes)] = entries[0] def cell_text(day, slot_index, duration_minutes, raw_by_day_slot=raw_by_day_slot): entries = raw_by_day_slot.get((day, slot_index, duration_minutes)) if not entries: return "" s = entries[0] tag = " (Lab)" if s["course_type"] == "lab" else (" (General)" if s["is_general"] else "") text = f'{s["course_name"]}{tag} [Sec {s["section_id"]}]\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: subtitle += f' ({batch["department"]} - {batch["program"]})' wb = new_workbook() ws = wb.active build_grid_sheet( ws, title=f"Roll No: {roll_no}", subtitle=subtitle, display_rows=DISPLAY_ROWS, sessions_by_cell=by_day_slot, cell_text_fn=cell_text, ) path = save_workbook(wb, output_dir, f"{roll_no}.xlsx") written.append(path) return written