commands
This commit is contained in:
@@ -1,12 +1,20 @@
|
||||
"""
|
||||
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>/.
|
||||
Main entry point: runs the three scheduling phases (general courses, labs,
|
||||
remaining courses) 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
|
||||
Each phase is its own subcommand so it can be run and inspected as a
|
||||
separate step; each phase's placements are persisted to
|
||||
result/schedule_state.json and loaded back in by the next phase's command.
|
||||
|
||||
Usage:
|
||||
python app/template_generator.py phase_1 # general courses (Tue/Thu preferred)
|
||||
python app/template_generator.py phase_2 # labs, on top of phase_1
|
||||
python app/template_generator.py phase_3 # remaining courses, batch by batch
|
||||
python app/template_generator.py clear_all # wipe state/results/log to start over
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
@@ -35,8 +43,43 @@ 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")
|
||||
STATE_FILE = os.path.join(ROOT_DIR, "result", "schedule_state.json")
|
||||
VIEW_NAMES = ["batch-wise", "department-wise", "room-wise", "student-wise", "teacher-wise"]
|
||||
|
||||
PHASE_ORDER = ["phase_1", "phase_2", "phase_3"]
|
||||
|
||||
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),
|
||||
]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# State persistence
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def load_state():
|
||||
"""Returns the saved schedule state dict, or None if none exists yet."""
|
||||
if not os.path.exists(STATE_FILE):
|
||||
return None
|
||||
with open(STATE_FILE, encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
|
||||
def save_state(scheduler, phases_completed):
|
||||
state = scheduler.to_state()
|
||||
state["phases_completed"] = phases_completed
|
||||
os.makedirs(os.path.dirname(STATE_FILE), exist_ok=True)
|
||||
with open(STATE_FILE, "w", encoding="utf-8") as f:
|
||||
json.dump(state, f, indent=2)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Shared helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _check_and_log(sessions, label):
|
||||
"""Runs student + teacher conflict checks over `sessions` and logs the result."""
|
||||
@@ -64,10 +107,7 @@ def clear_result_dir():
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
|
||||
def main():
|
||||
reset_log()
|
||||
clear_result_dir()
|
||||
|
||||
def ensure_dummy_data():
|
||||
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...")
|
||||
@@ -76,49 +116,101 @@ def main():
|
||||
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),
|
||||
]
|
||||
|
||||
def regenerate_templates(sessions, data):
|
||||
clear_result_dir()
|
||||
total_files = 0
|
||||
for view_name, generate_fn in generators:
|
||||
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"\n{total_files} timetable file(s) written under {RESULT_DIR}")
|
||||
|
||||
print(f"\nDone. {total_files} timetable file(s) written under {RESULT_DIR}")
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Commands
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def cmd_clear_all():
|
||||
if os.path.exists(STATE_FILE):
|
||||
os.remove(STATE_FILE)
|
||||
clear_result_dir()
|
||||
reset_log()
|
||||
log_info("clear_all: schedule state, result templates, and log have been reset. Run phase_1 to start over.")
|
||||
|
||||
|
||||
def cmd_phase(phase_name):
|
||||
ensure_dummy_data()
|
||||
data = load_data()
|
||||
|
||||
state = load_state()
|
||||
completed = state["phases_completed"] if state else []
|
||||
|
||||
if phase_name in completed:
|
||||
log_error(
|
||||
f"{phase_name} has already been run (completed phases: {completed}). "
|
||||
f"Run 'python app/template_generator.py clear_all' first if you want to redo it."
|
||||
)
|
||||
return
|
||||
|
||||
phase_index = PHASE_ORDER.index(phase_name)
|
||||
required_prior = PHASE_ORDER[:phase_index]
|
||||
missing = [p for p in required_prior if p not in completed]
|
||||
if missing:
|
||||
log_error(
|
||||
f"Cannot run {phase_name} yet — missing prior phase(s) {missing}. "
|
||||
f"Run them in order: {', '.join(PHASE_ORDER)} (or 'clear_all' to start over)."
|
||||
)
|
||||
return
|
||||
|
||||
if state is None:
|
||||
scheduler = Scheduler(data)
|
||||
else:
|
||||
scheduler = Scheduler.from_state(data, state)
|
||||
|
||||
if phase_name == "phase_1":
|
||||
log_info("Phase 1/3: scheduling general courses (Tue/Thu preferred)...")
|
||||
new_sessions = scheduler.phase_1_general_courses()
|
||||
elif phase_name == "phase_2":
|
||||
log_info("Phase 2/3: scheduling labs (on top of phase_1's fixed placements)...")
|
||||
new_sessions = scheduler.phase_2_labs()
|
||||
else:
|
||||
log_info("Phase 3/3: scheduling remaining courses (batch by batch)...")
|
||||
new_sessions = scheduler.phase_3_remaining_courses()
|
||||
|
||||
label = {
|
||||
"phase_1": "Phase 1 (general courses)",
|
||||
"phase_2": "Phase 2 (general courses + labs)",
|
||||
"phase_3": "Phase 3 (full schedule)",
|
||||
}[phase_name]
|
||||
_check_and_log(scheduler.sessions, label)
|
||||
|
||||
completed = completed + [phase_name]
|
||||
save_state(scheduler, completed)
|
||||
log_info(
|
||||
f"{phase_name} complete: {len(new_sessions)} session(s) added this phase, "
|
||||
f"{len(scheduler.sessions)} total so far. State saved to {STATE_FILE}."
|
||||
)
|
||||
|
||||
regenerate_templates(scheduler.sessions, data)
|
||||
|
||||
|
||||
def print_usage():
|
||||
print(__doc__)
|
||||
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
if not args or args[0] not in ("phase_1", "phase_2", "phase_3", "clear_all"):
|
||||
print_usage()
|
||||
return
|
||||
|
||||
command = args[0]
|
||||
if command == "clear_all":
|
||||
cmd_clear_all()
|
||||
else:
|
||||
cmd_phase(command)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user