File size: 7,067 Bytes
61ca3d8
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# 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"