timetable_gen / data_loader.py
KindAlien's picture
Upload 24 files
61ca3d8 verified
Raw
History Blame Contribute Delete
7.07 kB
# data_loader.py
"""
This module is responsible for converting raw input data into a list of
atomic 'Task' objects that the solver can schedule.
Responsibilities:
- Ingest lists of faculties, subjects, sections, and their allocations.
- Convert subject credits into the correct number of weekly Task instances.
- Ensure THEORY subjects create 1-hour tasks per credit.
- Ensure LAB, SOFTSKILL, and FORUM subjects create a single 2-hour task.
- Assign a unique ID to each task.
- Populate each task with its corresponding faculty, subject, and section.
- Correctly handle elective groups for multi-hour subjects.
This module performs NO scheduling logic, constraint creation, or optimization.
It is purely a data transformation and preparation layer.
"""
from dataclasses import dataclass
from typing import List, Optional
# Import the core data models
from models import Faculty, Subject, Section, Task, SubjectType
@dataclass(frozen=True)
class Allocation:
"""
A simple dataclass to represent the raw input mapping of who teaches what
to whom. This is a cleaner alternative to using tuples or dicts.
"""
faculty_id: str
subject_code: str
section_id: str
elective_group_id: Optional[str] = None
def prepare_scheduling_tasks(
allocations: List[Allocation],
faculties: List[Faculty],
subjects: List[Subject],
sections: List[Section]
) -> List[Task]:
"""
Processes raw allocation data and generates a flat list of atomic Task
objects ready for the solver.
Args:
allocations: A list of Allocation objects defining the teaching load.
faculties: A list of all available Faculty objects.
subjects: A list of all available Subject objects.
sections: A list of all available Section objects.
Returns:
A list of Task objects, where each task is an atomic unit to be scheduled.
"""
# Create lookup dictionaries for efficient access
faculties_by_id = {f.id: f for f in faculties}
subjects_by_code = {s.subject_code: s for s in subjects}
sections_by_id = {s.section_id: s for s in sections}
all_tasks: List[Task] = []
for alloc in allocations:
# Retrieve the full objects using the IDs from the allocation
try:
faculty = faculties_by_id[alloc.faculty_id]
subject = subjects_by_code[alloc.subject_code]
section = sections_by_id[alloc.section_id]
except KeyError as e:
print(f"Error: Invalid ID in allocation {alloc}. Missing key: {e}")
continue
# --- Task Generation Logic ---
if subject.subject_type == SubjectType.THEORY:
# For a theory subject, create one 1-hour task for each credit.
for i in range(subject.credits):
# *** CRITICAL FIX FOR ELECTIVES ***
# For a 3-credit elective, we need groups like 'group_0', 'group_1', 'group_2'
# to pair the correct hours across different sections.
group_id = None
if alloc.elective_group_id:
group_id = f"{alloc.elective_group_id}_{i}"
elif subject.is_core:
# Treat Core Theory as a "Joint Class" for all batches in the section (e.g. 6a-E1 + 6a-E2)
# We generate a synthetic group ID: CORE_CS101_6A_0
parent_sec = section.section_id.split('-')[0]
group_id = f"CORE_{subject.subject_code}_{parent_sec}_{i}"
task = Task(
task_id=f"{subject.subject_code}-{section.section_id}-{i}",
faculty=faculty,
subject=subject,
section=section,
duration=1,
elective_group_id=group_id
)
all_tasks.append(task)
elif subject.subject_type in [SubjectType.LAB, SubjectType.SOFTSKILL, SubjectType.FORUM]:
# For labs and other block sessions, create exactly ONE 2-hour task.
# The elective group applies to the entire 2-hour block as a single unit.
task = Task(
task_id=f"{subject.subject_code}-{section.section_id}-BLOCK",
faculty=faculty,
subject=subject,
section=section,
duration=2,
elective_group_id=alloc.elective_group_id
)
all_tasks.append(task)
return all_tasks
# --- Example Usage ---
if __name__ == '__main__':
# This block demonstrates how to use the prepare_scheduling_tasks function.
# It will only run when this file is executed directly.
# 1. Define sample data using the core models
faculty1 = Faculty(id="F001", name="Dr. Smith", designation="Professor", max_hours_per_week=10)
faculty2 = Faculty(id="F002", name="Dr. Jones", designation="Asst. Professor", max_hours_per_week=12)
subject_theory = Subject(subject_code="CS101", name="Intro to CS", credits=4, subject_type=SubjectType.THEORY)
subject_lab = Subject(subject_code="CS101L", name="CS Lab", credits=1, subject_type=SubjectType.LAB)
subject_elective = Subject(subject_code="CS555", name="Advanced AI", credits=3, subject_type=SubjectType.THEORY)
section_a = Section(section_id="5A", semester=5, student_strength=60)
section_b = Section(section_id="5B", semester=5, student_strength=62)
# 2. Define the teaching allocations
sample_allocations = [
Allocation(faculty_id="F001", subject_code="CS101", section_id="5A"),
Allocation(faculty_id="F002", subject_code="CS101L", section_id="5A"),
# Elective subject taught by the same faculty to both sections
Allocation(faculty_id="F001", subject_code="CS555", section_id="5A", elective_group_id="ELEC01"),
Allocation(faculty_id="F001", subject_code="CS555", section_id="5B", elective_group_id="ELEC01"),
]
# 3. Call the data loader function
generated_tasks = prepare_scheduling_tasks(
allocations=sample_allocations,
faculties=[faculty1, faculty2],
subjects=[subject_theory, subject_lab, subject_elective],
sections=[section_a, section_b]
)
# 4. Print the results to verify
print(f"--- Generated {len(generated_tasks)} Tasks ---\n")
for task in generated_tasks:
print(f"Task ID: {task.task_id}")
print(f" Subject: {task.subject.name} ({task.subject.subject_type.name})")
print(f" Faculty: {task.faculty.name}")
print(f" Section: {task.section.section_id}")
print(f" Duration: {task.duration} hour(s)")
if task.elective_group_id:
print(f" Elective Group: {task.elective_group_id}")
print("-" * 20)
# Expected Output:
# Elective tasks for CS555 will now have group IDs like "ELEC01_0", "ELEC01_1", "ELEC01_2"