126 lines
5.1 KiB
Python
126 lines
5.1 KiB
Python
"""
|
|
Main entry point: builds a clash-free dummy schedule and renders all five
|
|
timetable views (batch/department/room/student/teacher -wise) as .xlsx
|
|
files under result/templates/<view>/.
|
|
|
|
Run:
|
|
python app/template_generator.py
|
|
"""
|
|
|
|
import os
|
|
import shutil
|
|
import sys
|
|
|
|
APP_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
ROOT_DIR = os.path.dirname(APP_DIR)
|
|
|
|
sys.path.insert(0, APP_DIR)
|
|
sys.path.insert(0, os.path.join(ROOT_DIR, "controller"))
|
|
sys.path.insert(0, os.path.join(ROOT_DIR, "templates", "batch-wise"))
|
|
sys.path.insert(0, os.path.join(ROOT_DIR, "templates", "department-wise"))
|
|
sys.path.insert(0, os.path.join(ROOT_DIR, "templates", "room-wise"))
|
|
sys.path.insert(0, os.path.join(ROOT_DIR, "templates", "student-wise"))
|
|
sys.path.insert(0, os.path.join(ROOT_DIR, "templates", "teacher-wise"))
|
|
|
|
from template_controller import load_data, Scheduler # 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, log_warning # noqa: E402
|
|
|
|
from batch_wise_template import generate_batch_wise_templates # noqa: E402
|
|
from department_wise_template import generate_department_wise_templates # noqa: E402
|
|
from room_wise_template import generate_room_wise_templates # noqa: E402
|
|
from student_wise_template import generate_student_wise_templates # noqa: E402
|
|
from teacher_wise_template import generate_teacher_wise_templates # noqa: E402
|
|
|
|
RESULT_DIR = os.path.join(ROOT_DIR, "result", "templates")
|
|
VIEW_NAMES = ["batch-wise", "department-wise", "room-wise", "student-wise", "teacher-wise"]
|
|
|
|
|
|
def _check_and_log(sessions, label):
|
|
"""Runs student + teacher conflict checks over `sessions` and logs the result."""
|
|
report = check_all_conflicts(sessions)
|
|
if report["has_conflicts"]:
|
|
for line in format_student_conflicts(report["student_conflicts"]):
|
|
log_error(f"[{label}] STUDENT CONFLICT: {line}")
|
|
for line in format_teacher_conflicts(report["teacher_conflicts"]):
|
|
log_error(f"[{label}] TEACHER CONFLICT: {line}")
|
|
log_warning(
|
|
f"[{label}] Found {len(report['student_conflicts'])} student conflict(s) and "
|
|
f"{len(report['teacher_conflicts'])} teacher conflict(s)."
|
|
)
|
|
else:
|
|
log_info(f"[{label}] No conflicts found — clash-free.")
|
|
return report
|
|
|
|
|
|
def clear_result_dir():
|
|
"""Removes every previous run's output so stale files never linger."""
|
|
for view_name in VIEW_NAMES:
|
|
output_dir = os.path.join(RESULT_DIR, view_name)
|
|
if os.path.exists(output_dir):
|
|
shutil.rmtree(output_dir)
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
|
|
|
|
def main():
|
|
reset_log()
|
|
clear_result_dir()
|
|
|
|
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...")
|
|
sys.path.insert(0, os.path.join(ROOT_DIR, "data", "dummy"))
|
|
from generate_dummy_data import main as generate_dummy_data
|
|
generate_dummy_data()
|
|
print()
|
|
|
|
log_info("Loading data...")
|
|
data = load_data()
|
|
scheduler = Scheduler(data)
|
|
|
|
# --- Phase 1: general courses (Tue/Thu preferred), self-repaired until clash-free ---
|
|
log_info("Phase 1/3: scheduling general courses...")
|
|
phase1_sessions = scheduler.phase_1_general_courses()
|
|
_check_and_log(phase1_sessions, "Phase 1 (general courses)")
|
|
|
|
# --- Phase 2: labs, scheduled against Phase 1's fixed placements ---
|
|
log_info("Phase 2/3: scheduling labs...")
|
|
phase2_sessions = scheduler.phase_2_labs()
|
|
_check_and_log(phase1_sessions + phase2_sessions, "Phase 2 (general courses + labs)")
|
|
|
|
# --- Phase 3: remaining theory courses, batch by batch ---
|
|
log_info("Phase 3/3: scheduling remaining courses (batch by batch)...")
|
|
phase3_sessions = scheduler.phase_3_remaining_courses()
|
|
|
|
sessions = phase1_sessions + phase2_sessions + phase3_sessions
|
|
log_info(
|
|
f"Scheduling complete: {len(phase1_sessions)} general, {len(phase2_sessions)} lab, "
|
|
f"{len(phase3_sessions)} remaining = {len(sessions)} total session(s)."
|
|
)
|
|
|
|
log_info("Running final conflict check (student/batch and teacher) over the full schedule...")
|
|
_check_and_log(sessions, "Final (full schedule)")
|
|
|
|
generators = [
|
|
("batch-wise", generate_batch_wise_templates),
|
|
("department-wise", generate_department_wise_templates),
|
|
("room-wise", generate_room_wise_templates),
|
|
("student-wise", generate_student_wise_templates),
|
|
("teacher-wise", generate_teacher_wise_templates),
|
|
]
|
|
|
|
total_files = 0
|
|
for view_name, generate_fn in generators:
|
|
output_dir = os.path.join(RESULT_DIR, view_name)
|
|
written = generate_fn(sessions, data, output_dir)
|
|
total_files += len(written)
|
|
print(f"[{view_name}] generated {len(written)} file(s) -> {output_dir}")
|
|
|
|
print(f"\nDone. {total_files} timetable file(s) written under {RESULT_DIR}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|