""" Phase 1: general courses (shared across every batch of a session). Simple strategy — small offering count, Mon/Wed preferred first. After placing everything, self-repair: any batch left with two general-course sessions in the same slot has the losing session unmarked and re-placed (scanning every day) until this phase's own sessions are clash-free. """ from . import grid def run(board, data, preferred_days=("Monday", "Wednesday"), max_repair_attempts=20): """Places every general-course offering's weekly lectures. Returns this phase's sessions.""" general = [o for o in data["offerings"] if o["course"]["is_general"]] preferred_order = list(preferred_days) + [d for d in grid.DAYS if d not in preferred_days] phase_start = len(board.sessions) for offering in general: course = offering["course"] room_candidates = board.room_candidates(course["lecture_room_id"], board.classrooms) for _ in range(course["lectures_per_week"]): session = board.place_lecture(offering, room_candidates, preferred_order) if session is None: print(f"WARNING: could not place a lecture for general course {offering['course_id']} section {offering['section_id']}") phase_sessions = board.sessions[phase_start:] _repair_general_conflicts(board, data, phase_sessions, max_repair_attempts) return phase_sessions def _repair_general_conflicts(board, data, phase_sessions, max_attempts): """Moves general-course sessions that clash for a batch to a free slot on any day.""" from conflict_checker.student_conflict_checker import check_student_conflicts any_day_order = list(grid.DAYS) for _ in range(max_attempts): conflicts = check_student_conflicts(phase_sessions) if not conflicts: return # Re-place the last session involved in the first conflict; the # other session(s) in that cell stay put. losing_session = conflicts[0]["sessions"][-1] offering = next( o for o in data["offerings"] if o["course_id"] == losing_session["course_id"] and o["section_id"] == losing_session["section_id"] ) board.remove_session(losing_session) phase_sessions.remove(losing_session) room_candidates = board.room_candidates(offering["course"]["lecture_room_id"], board.classrooms) new_session = board.place_lecture(offering, room_candidates, any_day_order) if new_session is None: print(f"WARNING: could not repair general-course conflict for {offering['course_id']} section {offering['section_id']}") return phase_sessions.append(new_session) print("WARNING: general-course conflicts remained unresolved after max repair attempts")