""" Verifies that no batch (and therefore no student in that batch) is booked into two courses — lecture or lab — at the same day+slot. This is an independent verification pass over the scheduler's output: it does not trust the scheduler's own busy-maps, it re-derives every batch's occupied (day, slot_index) cells directly from the session list and flags any cell used by more than one session. """ from collections import defaultdict def check_student_conflicts(sessions): """ Returns a list of conflict dicts, one per (batch, day, slot) where two or more sessions overlap for that batch: { "batch_code": str, "day": str, "slot_index": int, "sessions": [session, session, ...], } Sessions that span multiple slot_indices (labs) occupy every one of their slot_indices, so a lab overlapping a lecture in either of its two slots is caught too. """ # (batch_code, day, slot_index) -> list of sessions occupying that cell occupied = defaultdict(list) for session in sessions: day = session["day"] for slot_index in session["slot_indices"]: for batch_code in session["batch_codes"]: occupied[(batch_code, day, slot_index)].append(session) conflicts = [] for (batch_code, day, slot_index), cell_sessions in occupied.items(): if len(cell_sessions) > 1: conflicts.append({ "batch_code": batch_code, "day": day, "slot_index": slot_index, "sessions": cell_sessions, }) conflicts.sort(key=lambda c: (c["batch_code"], c["day"], c["slot_index"])) return conflicts def format_student_conflicts(conflicts): """Human-readable lines describing each student/batch conflict.""" lines = [] for c in conflicts: course_list = ", ".join( f'{s["course_id"]} ({s["course_type"]}) with {s["instructor_name"]} in {s["room_id"]}' for s in c["sessions"] ) lines.append( f'Batch {c["batch_code"]} has {len(c["sessions"])} overlapping courses on ' f'{c["day"]} slot {c["slot_index"]}: {course_list}' ) return lines