KindAlien commited on
Commit
2da1ccd
·
0 Parent(s):

Super-squash branch 'main' using huggingface_hub

Browse files
.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install dependencies
6
+ COPY requirements.txt .
7
+ RUN pip install --no-cache-dir -r requirements.txt
8
+
9
+ # Copy all project files
10
+ COPY . .
11
+
12
+ # Expose both ports (7860 = FastAPI/public, 8501 = Streamlit/internal)
13
+ EXPOSE 7860
14
+ EXPOSE 8501
15
+
16
+ # Start FastAPI on 7860 (public) + Streamlit on 8501 (internal/background)
17
+ CMD streamlit run app.py --server.port 8501 --server.address 0.0.0.0 & \
18
+ uvicorn api:app --host 0.0.0.0 --port 7860
Procfile ADDED
@@ -0,0 +1 @@
 
 
1
+ web: uvicorn api:app --host 0.0.0.0 --port 7861 & streamlit run app.py --server.port 7860 --server.address 0.0.0.0
README.md ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Timetable Gen
3
+ emoji: 🗓️
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ app_port: 7860
8
+ ---
9
+
10
+ # Timetable Generator
api.py ADDED
@@ -0,0 +1,1866 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, HTTPException, File, UploadFile, Body
2
+ from fastapi.responses import StreamingResponse
3
+ from fastapi.middleware.cors import CORSMiddleware
4
+ from pydantic import BaseModel
5
+ from typing import List, Optional
6
+ from collections import defaultdict
7
+ import traceback
8
+ from datetime import datetime
9
+
10
+ from storage import (
11
+ load_data, save_data,
12
+ save_schedule, save_original_schedule,
13
+ schedule_exists, load_schedule,
14
+ original_schedule_exists, load_original_schedule,
15
+ load_history, add_history_entry, save_history,
16
+ clear_schedule, clear_history, clear_original_schedule,
17
+ save_version, load_versions, restore_version,
18
+ )
19
+ from models import Faculty, Subject, Section, Room, SubjectType
20
+ from data_loader import Allocation, prepare_scheduling_tasks
21
+ from solver import TimetableSolver
22
+ from partial_optimizer import PartialOptimizer
23
+ from slm_inference import get_constraints_batch, smart_parse, get_constraint, check_api_health
24
+ from substitution_engine import (
25
+ process_leave_approval, handle_acceptance, handle_decline, check_timeouts
26
+ )
27
+ from storage import (
28
+ load_leave_requests, save_leave_requests, load_substitution_requests,
29
+ save_substitution_requests, load_cancellations, save_cancellations
30
+ )
31
+ from models import LeaveRequest, LeaveStatus
32
+ import uuid
33
+ import constants as const
34
+
35
+ app = FastAPI(title="VTU Timetable Generator API", version="2.0.0")
36
+
37
+ app.add_middleware(
38
+ CORSMiddleware,
39
+ allow_origins=["*"],
40
+ allow_credentials=True,
41
+ allow_methods=["*"],
42
+ allow_headers=["*"],
43
+ )
44
+
45
+
46
+ # ═════════════════════════════════════════════════════════════════════════════
47
+ # INTERNAL HELPERS
48
+ # ═════════════════════════════════════════════════════════════════════════════
49
+
50
+ def _subject_type(type_str: str) -> SubjectType:
51
+ return {
52
+ "THEORY": SubjectType.THEORY,
53
+ "LAB": SubjectType.LAB,
54
+ "SOFTSKILL": SubjectType.SOFTSKILL,
55
+ "FORUM": SubjectType.FORUM,
56
+ }.get(type_str.upper(), SubjectType.THEORY)
57
+
58
+
59
+ def _build_objects(data: dict):
60
+ """Convert raw JSON data dicts into domain model objects."""
61
+ facs = [Faculty(f["id"], f["name"], f["designation"], f["max_hours"])
62
+ for f in data["faculties"]]
63
+ subs = [Subject(s["code"], s["name"], s["credits"],
64
+ _subject_type(s["type"]),
65
+ s.get("is_core", True), s.get("is_heavy", False))
66
+ for s in data["subjects"]]
67
+ secs = [Section(s["id"], s["semester"], s["strength"], s.get("home_room_id"))
68
+ for s in data["sections"]]
69
+ rooms = [Room(r["id"], r["capacity"], r["is_lab"], r["building"])
70
+ for r in data["rooms"]]
71
+ allocs = [Allocation(a["faculty_id"], a["subject_code"],
72
+ a["section_id"], a.get("elective_group"))
73
+ for a in data["allocations"]]
74
+ return facs, subs, secs, rooms, allocs
75
+
76
+
77
+ def _clean(solution: dict) -> dict:
78
+ """Remove non-serialisable task_obj from solution."""
79
+ return {k: {kk: vv for kk, vv in v.items() if kk != "task_obj"}
80
+ for k, v in solution.items()}
81
+
82
+ def diff_schedules(old_sched: dict, new_sched: dict):
83
+ changes = []
84
+ affected_sections = set()
85
+ all_keys = set(old_sched.keys()) | set(new_sched.keys())
86
+ for k in all_keys:
87
+ old_val = old_sched.get(k)
88
+ new_val = new_sched.get(k)
89
+ if old_val != new_val:
90
+ changes.append({
91
+ "task_id": k,
92
+ "before": old_val,
93
+ "after": new_val
94
+ })
95
+ if old_val: affected_sections.add(old_val.get("section_id", "").split("-")[0].upper())
96
+ if new_val: affected_sections.add(new_val.get("section_id", "").split("-")[0].upper())
97
+ return changes, list(affected_sections)
98
+
99
+
100
+ def _build_grid(solution: dict, allocations: list = None) -> dict:
101
+ """
102
+ Merge sub-sections (6a-E1, 6a-E2) into their parent (6A) —
103
+ exactly what render_timetable_html does in app.py.
104
+
105
+ Now also enriches grid entries with:
106
+ - duration: from the schedule entry
107
+ - elective_group: from allocations data
108
+ - is_open_elective: true if elective_group contains 'oe'
109
+ """
110
+ if allocations is None:
111
+ allocations = load_data().get("allocations", [])
112
+
113
+ # Build allocation lookup: (section_id, subject_code, faculty_id) -> elective_group
114
+ alloc_lookup = {}
115
+ if allocations:
116
+ for a in allocations:
117
+ key = (
118
+ a.get("section_id", "").lower(),
119
+ a.get("subject_code", "").lower(),
120
+ a.get("faculty_id", "").lower(),
121
+ )
122
+ alloc_lookup[key] = a.get("elective_group")
123
+
124
+ # Build faculty_id reverse lookup from data
125
+ # The schedule stores faculty_name (e.g. "Prof. Anu") but allocations use faculty_id (e.g. "anu")
126
+ # We'll also try matching by section_id + subject_code only as fallback
127
+ alloc_by_sec_sub = {}
128
+ if allocations:
129
+ for a in allocations:
130
+ key2 = (a.get("section_id", "").lower(), a.get("subject_code", "").lower())
131
+ alloc_by_sec_sub[key2] = a.get("elective_group")
132
+
133
+ parent_sections = sorted(set(
134
+ info.get("section_id", "").split("-")[0].upper()
135
+ for info in solution.values()
136
+ ))
137
+
138
+ merged = {ps: defaultdict(lambda: defaultdict(list)) for ps in parent_sections}
139
+ days_seen = {ps: set() for ps in parent_sections}
140
+
141
+ for task_id, info in solution.items():
142
+ sec_id = info.get("section_id") or ""
143
+ ps = sec_id.split("-")[0].upper() if sec_id else ""
144
+ day = info.get("day_index", 0)
145
+ period = info.get("period_index", 0)
146
+ dur = info.get("duration", 1)
147
+
148
+ subject_raw = info.get("subject_code") or "?"
149
+ subject = subject_raw.upper()
150
+
151
+ faculty = info.get("faculty_name") or ""
152
+ short_fac = (faculty.replace("Prof. ", "").replace("Dr. ", "")
153
+ .replace("Mr. ", "").replace("Ms. ", ""))
154
+
155
+ # Look up elective_group from allocations
156
+ eg = alloc_by_sec_sub.get((sec_id.lower(), subject_raw.lower()))
157
+ is_oe = bool(eg and "oe" in eg.lower())
158
+
159
+ days_seen[ps].add(day)
160
+
161
+ room_name = info.get("room_id") or info.get("room_name") or ""
162
+ for i in range(dur):
163
+ entry = {
164
+ "task_id": task_id,
165
+ "subject": subject,
166
+ "faculty": short_fac,
167
+ "room": room_name,
168
+ "room_id": info.get("room_id", ""),
169
+ "room_name": info.get("room_name", ""),
170
+ "duration": dur,
171
+ "elective_group": eg,
172
+ "is_open_elective": is_oe,
173
+ }
174
+ # Propagate substitution metadata so frontend can highlight
175
+ if info.get("is_substituted"):
176
+ entry["is_substituted"] = True
177
+ orig = info.get("original_faculty_name") or ""
178
+ entry["original_faculty"] = (orig.replace("Prof. ", "").replace("Dr. ", "")
179
+ .replace("Mr. ", "").replace("Ms. ", ""))
180
+ slot = merged[ps][day][period + i]
181
+ if not any(e["subject"] == entry["subject"] and e["faculty"] == entry["faculty"] for e in slot):
182
+ slot.append(entry)
183
+
184
+ grid = {}
185
+ for ps in parent_sections:
186
+ day_indices = list(range(len(const.DAYS))) # always Mon–Fri
187
+ if 5 in days_seen[ps]: # Saturday only if needed
188
+ day_indices.append(5)
189
+
190
+ slots = {}
191
+ for day in day_indices:
192
+ slots[str(day)] = {
193
+ str(p): merged[ps][day].get(p, [])
194
+ for p in range(const.NUM_TEACHING_SLOTS_PER_DAY)
195
+ }
196
+
197
+ grid[ps] = {"days": day_indices, "slots": slots}
198
+
199
+ return grid
200
+
201
+
202
+ def _timetable_constants() -> dict:
203
+ return {
204
+ "days": const.DAYS,
205
+ "headers": const.TIMETABLE_HEADERS,
206
+ "num_periods": const.NUM_TEACHING_SLOTS_PER_DAY,
207
+ "break_after_index": const.BREAK_AFTER_INDEX,
208
+ "lunch_after_index": const.LUNCH_AFTER_INDEX,
209
+ }
210
+
211
+
212
+ # ═════════════════════════════════════════════════════════════════════════════
213
+ # PYDANTIC SCHEMAS
214
+ # ═════════════════════════════════════════════════════════════════════════════
215
+
216
+ class FacultyIn(BaseModel):
217
+ id: str
218
+ name: str
219
+ designation: str
220
+ max_hours: int = 18
221
+
222
+ class SubjectIn(BaseModel):
223
+ code: str
224
+ name: str
225
+ credits: int
226
+ type: str = "THEORY" # THEORY | LAB | SOFTSKILL | FORUM
227
+ is_core: bool = True
228
+ is_heavy: bool = False
229
+
230
+ class SectionIn(BaseModel):
231
+ id: str
232
+ semester: int
233
+ strength: int
234
+ home_room_id: Optional[str] = None
235
+
236
+ class RoomIn(BaseModel):
237
+ id: str
238
+ capacity: int
239
+ is_lab: bool = False
240
+ building: str = "Main"
241
+
242
+ class AllocationIn(BaseModel):
243
+ faculty_id: str
244
+ subject_code: str
245
+ section_id: str
246
+ elective_group: Optional[str] = None
247
+
248
+ class GenerateRequest(BaseModel):
249
+ time_limit_seconds: int = 30
250
+ version_label: Optional[str] = None
251
+ semesters: Optional[List[int]] = None # e.g. [5, 7] for odd sems only
252
+ locked_semesters: Optional[List[int]] = None
253
+
254
+ class UpdateRequest(BaseModel):
255
+ prompt: str
256
+ preview_only: bool = False
257
+ propose_only: bool = False
258
+
259
+ class OverwriteRequest(BaseModel):
260
+ schedule: dict
261
+
262
+ class ProposeRequest(BaseModel):
263
+ schedule: dict
264
+ proposer: str
265
+ proposer_name: str
266
+ description: str = "Proposed timetable change"
267
+
268
+ class InjectEntry(BaseModel):
269
+ section_id: str # e.g. "6A" (parent section)
270
+ day_index: int # 0-4 (Mon-Fri)
271
+ period_index: int # 0-7 teaching period
272
+ subject_code: str # e.g. "ml"
273
+ faculty_name: str # e.g. "Dr. Kavitha"
274
+ duration: int = 1 # 1 for theory, 2 for lab
275
+ room_id: Optional[str] = None
276
+
277
+ class InjectRequest(BaseModel):
278
+ entries: list[InjectEntry]
279
+
280
+ class RemoveRequest(BaseModel):
281
+ task_id: str
282
+
283
+
284
+ # ═════════════════════════════════════════════════════════════════════════════
285
+ # HEALTH & CONSTANTS
286
+ # ═════════════════════════════════════════════════════════════════════════════
287
+
288
+ @app.get("/health")
289
+ def health():
290
+ """API liveness check — also returns timetable constants for convenience."""
291
+ return {
292
+ "status": "ok",
293
+ "schedule_exists": schedule_exists(),
294
+ **_timetable_constants(),
295
+ }
296
+
297
+ @app.get("/slm/health")
298
+ def slm_health():
299
+ """Check whether the external flan-t5 SLM API is reachable."""
300
+ ok = check_api_health()
301
+ return {"slm_online": ok, "message": "SLM API is online" if ok else "SLM API is offline"}
302
+
303
+ @app.get("/constants")
304
+ def get_constants():
305
+ """Timetable rendering constants (days, time headers, period indices)."""
306
+ return _timetable_constants()
307
+
308
+
309
+ # ═════════════════════════════════════════════════════════════════════════════
310
+ # ALL DATA
311
+ # ═════════════════════════════════════════════════════════════════════════════
312
+
313
+ @app.get("/data")
314
+ def get_all_data():
315
+ """Return all stored academic data in one call."""
316
+ return load_data()
317
+
318
+
319
+
320
+ import pandas as pd
321
+ import io
322
+
323
+ @app.get("/data/template/excel")
324
+ def get_excel_template():
325
+ from openpyxl.worksheet.datavalidation import DataValidation
326
+
327
+ df_faculties = pd.DataFrame(columns=["id", "name", "designation", "max_hours"])
328
+ df_subjects = pd.DataFrame(columns=["code", "name", "type", "credits", "is_core", "is_heavy"])
329
+ df_sections = pd.DataFrame(columns=["id", "semester", "strength", "home_room_id"])
330
+ df_rooms = pd.DataFrame(columns=["id", "capacity", "is_lab", "building"])
331
+ df_allocations = pd.DataFrame(columns=["faculty_id", "subject_code", "section_id", "elective_group"])
332
+ df_scheduling_rules = pd.DataFrame(columns=["rule_type", "faculty_id", "subject_codes", "subject_types", "period", "max_period", "days"])
333
+
334
+ output = io.BytesIO()
335
+ with pd.ExcelWriter(output, engine='openpyxl') as writer:
336
+ df_faculties.to_excel(writer, sheet_name='Faculties', index=False)
337
+ df_subjects.to_excel(writer, sheet_name='Subjects', index=False)
338
+ df_sections.to_excel(writer, sheet_name='Sections', index=False)
339
+ df_rooms.to_excel(writer, sheet_name='Rooms', index=False)
340
+ df_allocations.to_excel(writer, sheet_name='Allocations', index=False)
341
+ df_scheduling_rules.to_excel(writer, sheet_name='Scheduling Rules', index=False)
342
+
343
+ # Add dropdown validations to Scheduling Rules sheet
344
+ ws = writer.sheets['Scheduling Rules']
345
+
346
+ # Rule Type dropdown (column A, rows 2-100)
347
+ dv_rule_type = DataValidation(type="list", formula1='"FIXED_PERIOD,BEFORE_TIME,FIXED_DAYS,FACULTY_UNAVAILABLE"', allow_blank=True)
348
+ dv_rule_type.prompt = "Select a rule type"
349
+ dv_rule_type.promptTitle = "Rule Type"
350
+ ws.add_data_validation(dv_rule_type)
351
+ dv_rule_type.add(f'A2:A100')
352
+
353
+ # Subject Types dropdown (column D, rows 2-100)
354
+ dv_subject_types = DataValidation(type="list", formula1='"THEORY,LAB,SOFTSKILL,FORUM"', allow_blank=True)
355
+ dv_subject_types.prompt = "Select subject type(s) - comma-separate for multiple"
356
+ dv_subject_types.promptTitle = "Subject Types"
357
+ ws.add_data_validation(dv_subject_types)
358
+ dv_subject_types.add(f'D2:D100')
359
+
360
+ # Period dropdown (column E, rows 2-100)
361
+ dv_period = DataValidation(type="list", formula1='"Period 1,Period 2,Period 3,Period 4,Period 5,Period 6,Period 7,Period 8"', allow_blank=True)
362
+ dv_period.prompt = "Select period (for FIXED_PERIOD rules)"
363
+ dv_period.promptTitle = "Period"
364
+ ws.add_data_validation(dv_period)
365
+ dv_period.add(f'E2:E100')
366
+
367
+ # Max Period dropdown (column F, rows 2-100)
368
+ dv_max_period = DataValidation(type="list", formula1='"Period 1,Period 2,Period 3,Period 4,Period 5,Period 6,Period 7,Period 8"', allow_blank=True)
369
+ dv_max_period.prompt = "Select max period (for BEFORE_TIME rules)"
370
+ dv_max_period.promptTitle = "Max Period"
371
+ ws.add_data_validation(dv_max_period)
372
+ dv_max_period.add(f'F2:F100')
373
+
374
+ # Days dropdown (column G, rows 2-100)
375
+ dv_days = DataValidation(type="list", formula1='"MON,TUE,WED,THU,FRI,SAT"', allow_blank=True)
376
+ dv_days.prompt = "Select day(s) - comma-separate for multiple (for FIXED_DAYS rules)"
377
+ dv_days.promptTitle = "Days"
378
+ ws.add_data_validation(dv_days)
379
+ dv_days.add(f'G2:G100')
380
+
381
+ # Designation dropdown for Faculties sheet (column C, rows 2-100)
382
+ ws_fac = writer.sheets['Faculties']
383
+ dv_designation = DataValidation(type="list", formula1='"Professor,Assoc. Prof,Asst. Prof,Guest"', allow_blank=True)
384
+ dv_designation.prompt = "Select designation"
385
+ dv_designation.promptTitle = "Designation"
386
+ ws_fac.add_data_validation(dv_designation)
387
+ dv_designation.add(f'C2:C100')
388
+
389
+ # Subject Type dropdown for Subjects sheet (column C, rows 2-100)
390
+ ws_sub = writer.sheets['Subjects']
391
+ dv_sub_type = DataValidation(type="list", formula1='"THEORY,LAB,SOFTSKILL,FORUM"', allow_blank=True)
392
+ dv_sub_type.prompt = "Select subject type"
393
+ dv_sub_type.promptTitle = "Type"
394
+ ws_sub.add_data_validation(dv_sub_type)
395
+ dv_sub_type.add(f'C2:C100')
396
+
397
+ # Boolean dropdowns for Subjects (is_core col E, is_heavy col F)
398
+ dv_bool = DataValidation(type="list", formula1='"TRUE,FALSE"', allow_blank=True)
399
+ ws_sub.add_data_validation(dv_bool)
400
+ dv_bool.add(f'E2:F100')
401
+
402
+ # Boolean dropdown for Rooms is_lab (column C, rows 2-100)
403
+ ws_rooms = writer.sheets['Rooms']
404
+ dv_lab = DataValidation(type="list", formula1='"TRUE,FALSE"', allow_blank=True)
405
+ ws_rooms.add_data_validation(dv_lab)
406
+ dv_lab.add(f'C2:C100')
407
+
408
+ output.seek(0)
409
+ return StreamingResponse(
410
+ output,
411
+ media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
412
+ headers={"Content-Disposition": "attachment; filename=timetable_template.xlsx"}
413
+ )
414
+
415
+
416
+ @app.get("/data/export/excel")
417
+ def export_data_as_excel():
418
+ """Export all current data as an Excel file matching the template format."""
419
+ data = load_data()
420
+
421
+ # Build DataFrames from existing data
422
+ df_faculties = pd.DataFrame(data.get("faculties", []))
423
+ df_subjects = pd.DataFrame(data.get("subjects", []))
424
+ df_sections = pd.DataFrame(data.get("sections", []))
425
+ df_rooms = pd.DataFrame(data.get("rooms", []))
426
+ df_allocations = pd.DataFrame(data.get("allocations", []))
427
+
428
+ # Build scheduling rules DataFrame with human-readable columns
429
+ rules = data.get("scheduling_rules", [])
430
+ rules_rows = []
431
+ for rule in rules:
432
+ row = {
433
+ "rule_type": rule.get("rule_type", ""),
434
+ "faculty_id": rule.get("faculty_id", ""),
435
+ "subject_codes": ", ".join(rule.get("subject_codes", [])) if isinstance(rule.get("subject_codes"), list) else str(rule.get("subject_codes", "")),
436
+ "subject_types": ", ".join(rule.get("subject_types", [])) if isinstance(rule.get("subject_types"), list) else str(rule.get("subject_types", "")),
437
+ "period": f"Period {rule['period_index'] + 1}" if rule.get("period_index") is not None else "",
438
+ "max_period": f"Period {rule['max_period_index'] + 1}" if rule.get("max_period_index") is not None else "",
439
+ "days": ", ".join(rule.get("days", [])) if isinstance(rule.get("days"), list) else str(rule.get("days", "")),
440
+ }
441
+ rules_rows.append(row)
442
+ df_scheduling_rules = pd.DataFrame(rules_rows) if rules_rows else pd.DataFrame(columns=["rule_type", "subject_codes", "subject_types", "period", "max_period", "days"])
443
+
444
+ # Ensure column order matches the template
445
+ fac_cols = ["id", "name", "designation", "max_hours"]
446
+ sub_cols = ["code", "name", "type", "credits", "is_core", "is_heavy"]
447
+ sec_cols = ["id", "semester", "strength", "home_room_id"]
448
+ room_cols = ["id", "capacity", "is_lab", "building"]
449
+ alloc_cols = ["faculty_id", "subject_code", "section_id", "elective_group"]
450
+ rule_cols = ["rule_type", "faculty_id", "subject_codes", "subject_types", "period", "max_period", "days"]
451
+
452
+ for col in fac_cols:
453
+ if col not in df_faculties.columns:
454
+ df_faculties[col] = ""
455
+ for col in sub_cols:
456
+ if col not in df_subjects.columns:
457
+ df_subjects[col] = ""
458
+ for col in sec_cols:
459
+ if col not in df_sections.columns:
460
+ df_sections[col] = ""
461
+ for col in room_cols:
462
+ if col not in df_rooms.columns:
463
+ df_rooms[col] = ""
464
+ for col in alloc_cols:
465
+ if col not in df_allocations.columns:
466
+ df_allocations[col] = ""
467
+
468
+ output = io.BytesIO()
469
+ with pd.ExcelWriter(output, engine='openpyxl') as writer:
470
+ df_faculties[fac_cols].to_excel(writer, sheet_name='Faculties', index=False)
471
+ df_subjects[sub_cols].to_excel(writer, sheet_name='Subjects', index=False)
472
+ df_sections[sec_cols].to_excel(writer, sheet_name='Sections', index=False)
473
+ df_rooms[room_cols].to_excel(writer, sheet_name='Rooms', index=False)
474
+ df_allocations[alloc_cols].to_excel(writer, sheet_name='Allocations', index=False)
475
+ df_scheduling_rules[rule_cols].to_excel(writer, sheet_name='Scheduling Rules', index=False)
476
+
477
+ # Add dropdown validations (same as template)
478
+ from openpyxl.worksheet.datavalidation import DataValidation
479
+
480
+ # Faculties - designation dropdown
481
+ ws_fac = writer.sheets['Faculties']
482
+ dv_desig = DataValidation(type="list", formula1='"Professor,Assoc. Prof,Asst. Prof,Guest"', allow_blank=True)
483
+ ws_fac.add_data_validation(dv_desig)
484
+ dv_desig.add('C2:C1000')
485
+
486
+ # Subjects - type dropdown
487
+ ws_sub = writer.sheets['Subjects']
488
+ dv_stype = DataValidation(type="list", formula1='"THEORY,LAB,SOFTSKILL,FORUM"', allow_blank=True)
489
+ ws_sub.add_data_validation(dv_stype)
490
+ dv_stype.add('C2:C1000')
491
+
492
+ # Subjects - is_core & is_heavy boolean dropdowns
493
+ dv_bool_sub = DataValidation(type="list", formula1='"TRUE,FALSE"', allow_blank=True)
494
+ ws_sub.add_data_validation(dv_bool_sub)
495
+ dv_bool_sub.add('E2:F1000')
496
+
497
+ # Rooms - is_lab boolean dropdown
498
+ ws_rooms = writer.sheets['Rooms']
499
+ dv_lab = DataValidation(type="list", formula1='"TRUE,FALSE"', allow_blank=True)
500
+ ws_rooms.add_data_validation(dv_lab)
501
+ dv_lab.add('C2:C1000')
502
+
503
+ # Scheduling Rules - all dropdowns
504
+ ws_rules = writer.sheets['Scheduling Rules']
505
+
506
+ dv_rt = DataValidation(type="list", formula1='"FIXED_PERIOD,BEFORE_TIME,FIXED_DAYS"', allow_blank=True)
507
+ ws_rules.add_data_validation(dv_rt)
508
+ dv_rt.add('A2:A1000')
509
+
510
+ dv_st = DataValidation(type="list", formula1='"THEORY,LAB,SOFTSKILL,FORUM"', allow_blank=True)
511
+ ws_rules.add_data_validation(dv_st)
512
+ dv_st.add('C2:C1000')
513
+
514
+ dv_p = DataValidation(type="list", formula1='"Period 1,Period 2,Period 3,Period 4,Period 5,Period 6,Period 7,Period 8"', allow_blank=True)
515
+ ws_rules.add_data_validation(dv_p)
516
+ dv_p.add('D2:D1000')
517
+
518
+ dv_mp = DataValidation(type="list", formula1='"Period 1,Period 2,Period 3,Period 4,Period 5,Period 6,Period 7,Period 8"', allow_blank=True)
519
+ ws_rules.add_data_validation(dv_mp)
520
+ dv_mp.add('E2:E1000')
521
+
522
+ dv_d = DataValidation(type="list", formula1='"MON,TUE,WED,THU,FRI,SAT"', allow_blank=True)
523
+ ws_rules.add_data_validation(dv_d)
524
+ dv_d.add('F2:F1000')
525
+
526
+ output.seek(0)
527
+ return StreamingResponse(
528
+ output,
529
+ media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
530
+ headers={"Content-Disposition": "attachment; filename=timetable_data_export.xlsx"}
531
+ )
532
+
533
+ @app.post("/data/import/excel")
534
+ async def import_excel(file: UploadFile = File(...)):
535
+ contents = await file.read()
536
+ try:
537
+ xls = pd.ExcelFile(io.BytesIO(contents))
538
+ data = {
539
+ "faculties": [],
540
+ "subjects": [],
541
+ "sections": [],
542
+ "rooms": [],
543
+ "allocations": [],
544
+ "scheduling_rules": []
545
+ }
546
+
547
+ import re
548
+ def parse_int(val, default):
549
+ if pd.isna(val) or val == '': return default
550
+ try:
551
+ return int(val)
552
+ except (ValueError, TypeError):
553
+ m = re.search(r'\d+', str(val))
554
+ return int(m.group()) if m else default
555
+
556
+ if 'Faculties' in xls.sheet_names:
557
+ df = pd.read_excel(xls, 'Faculties').fillna('')
558
+ for _, row in df.iterrows():
559
+ if row.get('id'):
560
+ data["faculties"].append({
561
+ "id": str(row.get('id')),
562
+ "name": str(row.get('name', '')),
563
+ "designation": str(row.get('designation', 'Asst. Prof')),
564
+ "max_hours": parse_int(row.get('max_hours', 18), 18)
565
+ })
566
+
567
+ if 'Subjects' in xls.sheet_names:
568
+ df = pd.read_excel(xls, 'Subjects').fillna('')
569
+ for _, row in df.iterrows():
570
+ if row.get('code'):
571
+ data["subjects"].append({
572
+ "code": str(row.get('code')),
573
+ "name": str(row.get('name', '')),
574
+ "type": str(row.get('type', 'THEORY')),
575
+ "credits": parse_int(row.get('credits', 3), 3),
576
+ "is_core": bool(row.get('is_core', True)),
577
+ "is_heavy": bool(row.get('is_heavy', False))
578
+ })
579
+
580
+ if 'Sections' in xls.sheet_names:
581
+ df = pd.read_excel(xls, 'Sections').fillna('')
582
+ for _, row in df.iterrows():
583
+ if row.get('id'):
584
+ hr_val = row.get('home_room_id') or row.get('home_room')
585
+ data["sections"].append({
586
+ "id": str(row.get('id')),
587
+ "semester": parse_int(row.get('semester', 1), 1),
588
+ "strength": parse_int(row.get('strength', 60), 60),
589
+ "home_room_id": str(hr_val).strip() if hr_val and not pd.isna(hr_val) else None
590
+ })
591
+
592
+ if 'Rooms' in xls.sheet_names:
593
+ df = pd.read_excel(xls, 'Rooms').fillna('')
594
+ for _, row in df.iterrows():
595
+ if row.get('id'):
596
+ data["rooms"].append({
597
+ "id": str(row.get('id')),
598
+ "capacity": parse_int(row.get('capacity', 60), 60),
599
+ "is_lab": bool(row.get('is_lab', False)),
600
+ "building": str(row.get('building', 'Main'))
601
+ })
602
+
603
+ if 'Allocations' in xls.sheet_names:
604
+ df = pd.read_excel(xls, 'Allocations').fillna('')
605
+ for _, row in df.iterrows():
606
+ if row.get('faculty_id') and row.get('subject_code') and row.get('section_id'):
607
+ eg = row.get('elective_group')
608
+ data["allocations"].append({
609
+ "faculty_id": str(row.get('faculty_id')),
610
+ "subject_code": str(row.get('subject_code')),
611
+ "section_id": str(row.get('section_id')),
612
+ "elective_group": str(eg) if eg else None
613
+ })
614
+
615
+ # Parse Scheduling Rules sheet if present, otherwise preserve existing rules
616
+ if 'Scheduling Rules' in xls.sheet_names:
617
+ import uuid as _uuid
618
+ df = pd.read_excel(xls, 'Scheduling Rules').fillna('')
619
+ for _, row in df.iterrows():
620
+ rule_type = str(row.get('rule_type', '')).strip()
621
+ if not rule_type:
622
+ continue
623
+ rule = {"id": str(_uuid.uuid4()), "rule_type": rule_type}
624
+
625
+ # Parse faculty_id
626
+ fid = str(row.get('faculty_id', '')).strip()
627
+ if fid:
628
+ rule["faculty_id"] = fid
629
+
630
+ # Parse subject_codes (comma-separated)
631
+ sc = str(row.get('subject_codes', '')).strip()
632
+ rule["subject_codes"] = [s.strip() for s in sc.split(',') if s.strip()] if sc else []
633
+
634
+ # Parse subject_types (comma-separated)
635
+ st = str(row.get('subject_types', '')).strip()
636
+ rule["subject_types"] = [s.strip() for s in st.split(',') if s.strip()] if st else []
637
+
638
+ # Parse period (e.g. "Period 3" -> 2)
639
+ period_str = str(row.get('period', '')).strip()
640
+ if period_str and 'Period' in period_str:
641
+ try:
642
+ rule["period_index"] = int(period_str.replace('Period ', '')) - 1
643
+ except ValueError:
644
+ pass
645
+
646
+ # Parse max_period
647
+ max_period_str = str(row.get('max_period', '')).strip()
648
+ if max_period_str and 'Period' in max_period_str:
649
+ try:
650
+ rule["max_period_index"] = int(max_period_str.replace('Period ', '')) - 1
651
+ except ValueError:
652
+ pass
653
+
654
+ # Parse days (comma-separated)
655
+ days_str = str(row.get('days', '')).strip()
656
+ if days_str:
657
+ rule["days"] = [d.strip() for d in days_str.split(',') if d.strip()]
658
+
659
+ data["scheduling_rules"].append(rule)
660
+ else:
661
+ # Preserve existing scheduling_rules if no sheet in upload
662
+ existing_data = load_data()
663
+ data["scheduling_rules"] = existing_data.get("scheduling_rules", [])
664
+
665
+ save_data(data)
666
+ return {"message": "Data imported successfully", "imported": {k: len(v) for k, v in data.items()}}
667
+
668
+ except Exception as e:
669
+ raise HTTPException(400, f"Failed to parse Excel file: {str(e)}")
670
+
671
+ # ═════════════════════════════════════════════════════════════════════════════
672
+ # MANAGE FACULTIES
673
+ # ═════════════════════════════════════════════════════════════════════════════
674
+
675
+ @app.get("/data/faculties")
676
+ def list_faculties():
677
+ return {"faculties": load_data()["faculties"]}
678
+
679
+ @app.post("/data/faculties", status_code=201)
680
+ def add_faculty(faculty: FacultyIn):
681
+ data = load_data()
682
+ if any(f["id"] == faculty.id for f in data["faculties"]):
683
+ raise HTTPException(400, f"Faculty ID '{faculty.id}' already exists.")
684
+ data["faculties"].append(faculty.model_dump())
685
+ save_data(data)
686
+ return {"message": "Faculty added.", "faculty": faculty.model_dump()}
687
+
688
+
689
+ @app.put("/data/faculties/{faculty_id}")
690
+ def update_faculty(faculty_id: str, faculty: FacultyIn):
691
+ data = load_data()
692
+ for i, f in enumerate(data["faculties"]):
693
+ if f["id"] == faculty_id:
694
+ updated = faculty.model_dump()
695
+ data["faculties"][i] = updated
696
+ # Cascade to allocations and scheduling_rules if ID changed
697
+ if faculty_id != updated["id"]:
698
+ for a in data.get("allocations", []):
699
+ if a.get("faculty_id") == faculty_id:
700
+ a["faculty_id"] = updated["id"]
701
+ for r in data.get("scheduling_rules", []):
702
+ if r.get("faculty_id") == faculty_id:
703
+ r["faculty_id"] = updated["id"]
704
+ save_data(data)
705
+ return {"message": "Faculty updated.", "faculty": updated}
706
+ raise HTTPException(404, f"Faculty '{faculty_id}' not found.")
707
+
708
+ @app.delete("/data/faculties")
709
+ def clear_faculties():
710
+ data = load_data()
711
+ data["faculties"] = []
712
+ save_data(data)
713
+ return {"message": "All faculties cleared."}
714
+
715
+ @app.delete("/data/faculties/{faculty_id}")
716
+ def delete_faculty(faculty_id: str):
717
+ data = load_data()
718
+ before = len(data["faculties"])
719
+ data["faculties"] = [f for f in data["faculties"] if f["id"] != faculty_id]
720
+ if len(data["faculties"]) == before:
721
+ raise HTTPException(404, f"Faculty '{faculty_id}' not found.")
722
+ save_data(data)
723
+ return {"message": f"Faculty '{faculty_id}' deleted."}
724
+
725
+
726
+ # ═════════════════════════════════════════════════════════════════════════════
727
+ # MANAGE SUBJECTS
728
+ # ═════════════════════════════════════════════════════════════════════════════
729
+
730
+ @app.get("/data/subjects")
731
+ def list_subjects():
732
+ return {"subjects": load_data()["subjects"]}
733
+
734
+ @app.post("/data/subjects", status_code=201)
735
+ def add_subject(subject: SubjectIn):
736
+ data = load_data()
737
+ if any(s["code"] == subject.code for s in data["subjects"]):
738
+ raise HTTPException(400, f"Subject code '{subject.code}' already exists.")
739
+ data["subjects"].append(subject.model_dump())
740
+ save_data(data)
741
+ return {"message": "Subject added.", "subject": subject.model_dump()}
742
+
743
+
744
+ @app.put("/data/subjects/{subject_code}")
745
+ def update_subject(subject_code: str, subject: SubjectIn):
746
+ data = load_data()
747
+ for i, s in enumerate(data["subjects"]):
748
+ if s["code"] == subject_code:
749
+ updated = subject.model_dump()
750
+ data["subjects"][i] = updated
751
+ # Cascade to allocations and scheduling_rules if code changed
752
+ if subject_code != updated["code"]:
753
+ for a in data.get("allocations", []):
754
+ if a.get("subject_code") == subject_code:
755
+ a["subject_code"] = updated["code"]
756
+ for r in data.get("scheduling_rules", []):
757
+ if subject_code in r.get("subject_codes", []):
758
+ r["subject_codes"] = [updated["code"] if c == subject_code else c for c in r["subject_codes"]]
759
+ save_data(data)
760
+ return {"message": "Subject updated.", "subject": updated}
761
+ raise HTTPException(404, f"Subject '{subject_code}' not found.")
762
+
763
+ @app.delete("/data/subjects")
764
+ def clear_subjects():
765
+ data = load_data()
766
+ data["subjects"] = []
767
+ save_data(data)
768
+ return {"message": "All subjects cleared."}
769
+
770
+ @app.delete("/data/subjects/{subject_code}")
771
+ def delete_subject(subject_code: str):
772
+ data = load_data()
773
+ before = len(data["subjects"])
774
+ data["subjects"] = [s for s in data["subjects"] if s["code"] != subject_code]
775
+ if len(data["subjects"]) == before:
776
+ raise HTTPException(404, f"Subject '{subject_code}' not found.")
777
+ save_data(data)
778
+ return {"message": f"Subject '{subject_code}' deleted."}
779
+
780
+
781
+ # ═════════════════════════════════════════════════════════════════════════════
782
+ # MANAGE SECTIONS
783
+ # ═════════════════════════════════════════════════════════════════════════════
784
+
785
+ @app.get("/data/sections")
786
+ def list_sections():
787
+ return {"sections": load_data()["sections"]}
788
+
789
+ @app.post("/data/sections", status_code=201)
790
+ def add_section(section: SectionIn):
791
+ data = load_data()
792
+ if any(s["id"] == section.id for s in data["sections"]):
793
+ raise HTTPException(400, f"Section ID '{section.id}' already exists.")
794
+ data["sections"].append(section.model_dump())
795
+ save_data(data)
796
+ return {"message": "Section added.", "section": section.model_dump()}
797
+
798
+
799
+ @app.put("/data/sections/{section_id}")
800
+ def update_section(section_id: str, section: SectionIn):
801
+ data = load_data()
802
+ for i, s in enumerate(data["sections"]):
803
+ if s["id"] == section_id:
804
+ updated = section.model_dump()
805
+ data["sections"][i] = updated
806
+ # Cascade to allocations if ID changed
807
+ if section_id != updated["id"]:
808
+ for a in data.get("allocations", []):
809
+ if a.get("section_id") == section_id:
810
+ a["section_id"] = updated["id"]
811
+ save_data(data)
812
+ return {"message": "Section updated.", "section": updated}
813
+ raise HTTPException(404, f"Section '{section_id}' not found.")
814
+
815
+ @app.delete("/data/sections")
816
+ def clear_sections():
817
+ data = load_data()
818
+ data["sections"] = []
819
+ save_data(data)
820
+ return {"message": "All sections cleared."}
821
+
822
+ @app.get("/data/semesters")
823
+ def list_semesters():
824
+ """Return all unique semester numbers from sections data."""
825
+ data = load_data()
826
+ semesters = sorted(set(s["semester"] for s in data.get("sections", [])))
827
+ return {"semesters": semesters}
828
+
829
+
830
+ @app.delete("/data/sections/{section_id}")
831
+ def delete_section(section_id: str):
832
+ data = load_data()
833
+ before = len(data["sections"])
834
+ data["sections"] = [s for s in data["sections"] if s["id"] != section_id]
835
+ if len(data["sections"]) == before:
836
+ raise HTTPException(404, f"Section '{section_id}' not found.")
837
+ save_data(data)
838
+ return {"message": f"Section '{section_id}' deleted."}
839
+
840
+
841
+ # ═════════════════════════════════════════════════════════════════════════════
842
+ # MANAGE ROOMS
843
+ # ═════════════════════════════════════════════════════════════════════════════
844
+
845
+ @app.get("/data/rooms")
846
+ def list_rooms():
847
+ return {"rooms": load_data()["rooms"]}
848
+
849
+ @app.post("/data/rooms", status_code=201)
850
+ def add_room(room: RoomIn):
851
+ data = load_data()
852
+ if any(r["id"] == room.id for r in data["rooms"]):
853
+ raise HTTPException(400, f"Room ID '{room.id}' already exists.")
854
+ data["rooms"].append(room.model_dump())
855
+ save_data(data)
856
+ return {"message": "Room added.", "room": room.model_dump()}
857
+
858
+
859
+ @app.put("/data/rooms/{room_id}")
860
+ def update_room(room_id: str, room: RoomIn):
861
+ data = load_data()
862
+ for i, r in enumerate(data["rooms"]):
863
+ if r["id"] == room_id:
864
+ updated = room.model_dump()
865
+ data["rooms"][i] = updated
866
+ save_data(data)
867
+ return {"message": "Room updated.", "room": updated}
868
+ raise HTTPException(404, f"Room '{room_id}' not found.")
869
+
870
+ @app.delete("/data/rooms")
871
+ def clear_rooms():
872
+ data = load_data()
873
+ data["rooms"] = []
874
+ save_data(data)
875
+ return {"message": "All rooms cleared."}
876
+
877
+ @app.delete("/data/rooms/{room_id}")
878
+ def delete_room(room_id: str):
879
+ data = load_data()
880
+ before = len(data["rooms"])
881
+ data["rooms"] = [r for r in data["rooms"] if r["id"] != room_id]
882
+ if len(data["rooms"]) == before:
883
+ raise HTTPException(404, f"Room '{room_id}' not found.")
884
+ save_data(data)
885
+ return {"message": f"Room '{room_id}' deleted."}
886
+
887
+
888
+ # ═════════════════════════════════════════════════════════════════════════════
889
+ # MANAGE ALLOCATIONS
890
+ # ═════════════════════════════════════════════════════════════════════════════
891
+
892
+ @app.get("/data/allocations")
893
+ def list_allocations():
894
+ return {"allocations": load_data()["allocations"]}
895
+
896
+ @app.post("/data/allocations", status_code=201)
897
+ def add_allocation(alloc: AllocationIn):
898
+ data = load_data()
899
+ data["allocations"].append(alloc.model_dump())
900
+ save_data(data)
901
+ return {"message": "Allocation added.", "allocation": alloc.model_dump()}
902
+
903
+
904
+ @app.put("/data/allocations/{idx}")
905
+ def update_allocation(idx: int, alloc: AllocationIn):
906
+ data = load_data()
907
+ if idx < 0 or idx >= len(data["allocations"]):
908
+ raise HTTPException(404, f"Allocation index {idx} out of range.")
909
+ data["allocations"][idx] = alloc.model_dump()
910
+ save_data(data)
911
+ return {"message": "Allocation updated.", "allocation": alloc.model_dump()}
912
+
913
+ @app.delete("/data/allocations")
914
+ def clear_allocations():
915
+ data = load_data()
916
+ data["allocations"] = []
917
+ save_data(data)
918
+ return {"message": "All allocations cleared."}
919
+
920
+ @app.delete("/data/allocations/{idx}")
921
+ def delete_allocation(idx: int):
922
+ """Delete allocation by its 0-based index in the list."""
923
+ data = load_data()
924
+ if idx < 0 or idx >= len(data["allocations"]):
925
+ raise HTTPException(404, f"Allocation index {idx} out of range.")
926
+ removed = data["allocations"].pop(idx)
927
+ save_data(data)
928
+ return {"message": "Allocation deleted.", "removed": removed}
929
+
930
+
931
+ # ═════════════════════════════════════════════════════════════════════════════
932
+ # SCHEDULING RULES
933
+ # ═════════════════════════════════════════════════════════════════════════════
934
+
935
+ @app.get("/scheduling-rules")
936
+ def get_scheduling_rules():
937
+ data = load_data()
938
+ return {"rules": data.get("scheduling_rules", [])}
939
+
940
+ @app.post("/scheduling-rules")
941
+ def add_scheduling_rule(rule: dict):
942
+ import uuid as _uuid
943
+ data = load_data()
944
+ rules = data.get("scheduling_rules", [])
945
+ rule["id"] = str(_uuid.uuid4())
946
+ rules.append(rule)
947
+ data["scheduling_rules"] = rules
948
+ save_data(data)
949
+ return {"message": "Rule added.", "rule": rule}
950
+
951
+
952
+ @app.put("/scheduling-rules/{rule_id}")
953
+ def update_scheduling_rule(rule_id: str, rule: dict):
954
+ data = load_data()
955
+ rules = data.get("scheduling_rules", [])
956
+ for i, r in enumerate(rules):
957
+ if r.get("id") == rule_id:
958
+ rule["id"] = rule_id
959
+ rules[i] = rule
960
+ data["scheduling_rules"] = rules
961
+ save_data(data)
962
+ return {"message": "Rule updated.", "rule": rule}
963
+ raise HTTPException(404, "Rule not found.")
964
+
965
+ @app.delete("/scheduling-rules/{rule_id}")
966
+ def delete_scheduling_rule(rule_id: str):
967
+ data = load_data()
968
+ rules = data.get("scheduling_rules", [])
969
+ data["scheduling_rules"] = [r for r in rules if r.get("id") != rule_id]
970
+ save_data(data)
971
+ return {"message": "Rule deleted."}
972
+
973
+
974
+ # ═════════════════════════════════════════════════════════════════════════════
975
+ # GENERATE TIMETABLE
976
+ # ═════════════════════════════════════════════════════════════════════════════
977
+
978
+ @app.post("/generate")
979
+ def generate(req: GenerateRequest):
980
+ try:
981
+ data = load_data()
982
+
983
+ missing = [k for k in ("faculties", "subjects", "sections", "rooms", "allocations")
984
+ if not data.get(k)]
985
+ if missing:
986
+ raise HTTPException(400, f"Missing data for: {', '.join(missing)}.")
987
+
988
+ # ── Semester filtering ────────────────────────────────────────────
989
+ has_sems = bool(req.semesters)
990
+ has_locked = bool(req.locked_semesters)
991
+ if has_sems or has_locked:
992
+ selected_sems = set(req.semesters or [])
993
+ locked_sems = set(req.locked_semesters or [])
994
+ combined_sems = selected_sems.union(locked_sems)
995
+
996
+ if combined_sems:
997
+ # 1. Filter sections to only combined semesters
998
+ data["sections"] = [
999
+ s for s in data["sections"] if s["semester"] in combined_sems
1000
+ ]
1001
+ if not data["sections"]:
1002
+ raise HTTPException(400, f"No sections found for semesters: {combined_sems}")
1003
+
1004
+ # 2. Filter allocations to only reference surviving sections
1005
+ valid_section_ids = {s["id"] for s in data["sections"]}
1006
+ data["allocations"] = [
1007
+ a for a in data["allocations"]
1008
+ if a["section_id"] in valid_section_ids
1009
+ ]
1010
+
1011
+ # 3. Filter subjects to only those referenced by surviving allocations
1012
+ used_subject_codes = {a["subject_code"] for a in data["allocations"]}
1013
+ data["subjects"] = [
1014
+ s for s in data["subjects"] if s["code"] in used_subject_codes
1015
+ ]
1016
+
1017
+ # 4. Filter faculties to only those referenced by surviving allocations
1018
+ used_faculty_ids = {a["faculty_id"] for a in data["allocations"]}
1019
+ data["faculties"] = [
1020
+ f for f in data["faculties"] if f["id"] in used_faculty_ids
1021
+ ]
1022
+
1023
+ print(f"[generate] Filtered to combined semesters {combined_sems}: "
1024
+ f"{len(data['sections'])} sections, {len(data['allocations'])} allocations, "
1025
+ f"{len(data['subjects'])} subjects, {len(data['faculties'])} faculties")
1026
+
1027
+ facs, subs, secs, rooms, allocs = _build_objects(data)
1028
+ tasks = prepare_scheduling_tasks(allocs, facs, subs, secs)
1029
+
1030
+ if not tasks:
1031
+ raise HTTPException(400, "No schedulable tasks found. Check your allocations.")
1032
+
1033
+ locked_schedule = {}
1034
+ if req.locked_semesters:
1035
+ if schedule_exists():
1036
+ locked_schedule = load_schedule()
1037
+ else:
1038
+ raise HTTPException(400, "Cannot lock semesters: no existing schedule found.")
1039
+
1040
+ solver = TimetableSolver(tasks, facs, secs, rooms)
1041
+ try:
1042
+ status, solution = solver.solve(
1043
+ time_limit_seconds=req.time_limit_seconds,
1044
+ enable_soft_constraints=True,
1045
+ scheduling_rules=data.get("scheduling_rules", []),
1046
+ locked_schedule=locked_schedule,
1047
+ locked_semesters=req.locked_semesters
1048
+ )
1049
+ except ValueError as e:
1050
+ raise HTTPException(status_code=400, detail=str(e))
1051
+
1052
+ if status not in ("OPTIMAL", "FEASIBLE"):
1053
+ diagnosis = solver.diagnose_infeasibility()
1054
+ raise HTTPException(400, f"Timetable generation failed. {diagnosis}")
1055
+
1056
+ # Auto-save current schedule as a version before overwriting
1057
+ if schedule_exists():
1058
+ save_version(label=req.version_label)
1059
+
1060
+ save_schedule(solution)
1061
+ save_original_schedule(solution)
1062
+ clean = _clean(solution)
1063
+
1064
+ return {
1065
+ "status": status,
1066
+ "task_count": len(tasks),
1067
+ "semesters_generated": req.semesters or "all",
1068
+ "schedule": clean,
1069
+ "grid": _build_grid(clean),
1070
+ **_timetable_constants(),
1071
+ }
1072
+
1073
+ except HTTPException:
1074
+ raise
1075
+ except Exception:
1076
+ raise HTTPException(500, traceback.format_exc())
1077
+
1078
+
1079
+ # ═════════════════════════════════════════════════════════════════════════════
1080
+ # GET / DELETE TIMETABLE
1081
+ # ═════════════════════════════════════════════════════════════════════════════
1082
+
1083
+ @app.get("/schedule")
1084
+ def get_schedule():
1085
+ if not schedule_exists():
1086
+ return {"exists": False, "schedule": None, "grid": None}
1087
+ data = load_schedule()
1088
+ clean = _clean(data["schedule"])
1089
+ return {
1090
+ "exists": True,
1091
+ "generated_at": data.get("generated_at"),
1092
+ "schedule": clean,
1093
+ "grid": _build_grid(clean),
1094
+ **_timetable_constants(),
1095
+ }
1096
+
1097
+ @app.get("/schedule/original")
1098
+ def get_original_schedule():
1099
+ if not original_schedule_exists():
1100
+ return {"exists": False, "grid": None}
1101
+ data = load_original_schedule()
1102
+ clean = _clean(data["schedule"])
1103
+ return {
1104
+ "exists": True,
1105
+ "generated_at": data.get("generated_at"),
1106
+ "grid": _build_grid(clean),
1107
+ **_timetable_constants(),
1108
+ }
1109
+
1110
+ @app.delete("/schedule")
1111
+ def delete_schedule(version_label: Optional[str] = None):
1112
+ # Auto-save as version before clearing
1113
+ if schedule_exists():
1114
+ save_version(label=version_label)
1115
+ clear_schedule()
1116
+ clear_original_schedule()
1117
+ clear_history()
1118
+ return {"message": "Schedule saved as version and cleared."}
1119
+
1120
+ @app.post("/schedule/revert")
1121
+ def revert_schedule():
1122
+ if not original_schedule_exists():
1123
+ raise HTTPException(400, "No original schedule found to revert to.")
1124
+ data = load_original_schedule()
1125
+ save_schedule(data["schedule"])
1126
+ clear_history()
1127
+ clean = _clean(data["schedule"])
1128
+ return {
1129
+ "status": "SUCCESS",
1130
+ "message": "Reverted to original schedule and cleared history.",
1131
+ "schedule": clean,
1132
+ "grid": _build_grid(clean),
1133
+ **_timetable_constants(),
1134
+ }
1135
+
1136
+ # ═════════════════════════════════════════════════════════════════════════════
1137
+ # SCHEDULE VERSIONS
1138
+ # ═════════════════════════════════════════════════════════════════════════════
1139
+
1140
+ @app.get("/schedule/versions")
1141
+ def get_versions():
1142
+ versions = load_versions()
1143
+ # Return summary only (not full schedule data) for the list view
1144
+ return {
1145
+ "versions": [
1146
+ {
1147
+ "id": v["id"],
1148
+ "label": v.get("label", f"Version {i+1}"),
1149
+ "timestamp": v.get("timestamp", ""),
1150
+ "generated_at": v.get("generated_at", ""),
1151
+ "history_count": len(v.get("history", [])),
1152
+ }
1153
+ for i, v in enumerate(versions)
1154
+ ]
1155
+ }
1156
+
1157
+ @app.get("/schedule/versions/{version_id}")
1158
+ def get_version_details(version_id: str):
1159
+ versions = load_versions()
1160
+ target = next((v for v in versions if v["id"] == version_id), None)
1161
+ if not target:
1162
+ raise HTTPException(404, "Version not found.")
1163
+ clean = _clean(target.get("schedule", {}))
1164
+ return {
1165
+ "status": "SUCCESS",
1166
+ "version_id": version_id,
1167
+ "label": target.get("label"),
1168
+ "schedule": clean,
1169
+ "grid": _build_grid(clean),
1170
+ **_timetable_constants(),
1171
+ }
1172
+
1173
+ @app.post("/schedule/versions/restore/{version_id}")
1174
+ def restore_version_endpoint(version_id: str):
1175
+ # Auto-save current as a version before restoring
1176
+ if schedule_exists():
1177
+ save_version()
1178
+ result = restore_version(version_id)
1179
+ if not result:
1180
+ raise HTTPException(404, "Version not found.")
1181
+ clean = _clean(result["schedule"])
1182
+ return {
1183
+ "status": "SUCCESS",
1184
+ "message": f"Restored version: {result.get('label', version_id)}",
1185
+ "schedule": clean,
1186
+ "grid": _build_grid(clean),
1187
+ **_timetable_constants(),
1188
+ }
1189
+
1190
+ @app.post("/schedule/overwrite")
1191
+ def overwrite_schedule(req: OverwriteRequest):
1192
+ if not req.schedule:
1193
+ raise HTTPException(400, "Schedule payload cannot be empty.")
1194
+
1195
+ old_raw = load_schedule()
1196
+ old_schedule = _clean(old_raw.get("schedule", {})) if old_raw else {}
1197
+ clean_new = _clean(req.schedule)
1198
+
1199
+ changes, affected = diff_schedules(old_schedule, clean_new)
1200
+
1201
+ save_schedule(clean_new)
1202
+ if changes:
1203
+ add_history_entry(
1204
+ operation_type="MANUAL_OVERWRITE",
1205
+ description=f"Manual drag & drop modifications ({len(changes)} cells affected)",
1206
+ affected_sections=affected,
1207
+ changes=changes,
1208
+ status="SUCCESS",
1209
+ constraints=[]
1210
+ )
1211
+ return {
1212
+ "status": "SUCCESS",
1213
+ "message": "Schedule overwritten manually.",
1214
+ "schedule": clean_new,
1215
+ "grid": _build_grid(clean_new),
1216
+ **_timetable_constants(),
1217
+ }
1218
+
1219
+
1220
+ # ═════════════════════════════════════════════════════════════════════════════
1221
+ # SCHEDULE PROPOSALS (SUPER TEACHER WORKFLOW)
1222
+ # ═════════════════════════════════════════════════════════════════════════════
1223
+
1224
+ @app.get("/schedule/proposals")
1225
+ def get_proposals():
1226
+ from storage import load_proposals
1227
+ return {"proposals": load_proposals()}
1228
+
1229
+ @app.post("/schedule/propose")
1230
+ def propose_schedule(req: ProposeRequest):
1231
+ from storage import save_proposal
1232
+ import uuid as _uuid
1233
+
1234
+ old_raw = load_schedule()
1235
+ old_schedule = _clean(old_raw.get("schedule", {})) if old_raw else {}
1236
+ clean_new = _clean(req.schedule)
1237
+
1238
+ changes, affected = diff_schedules(old_schedule, clean_new)
1239
+
1240
+ proposal = {
1241
+ "id": str(_uuid.uuid4()),
1242
+ "proposer": req.proposer,
1243
+ "proposer_name": req.proposer_name,
1244
+ "description": req.description,
1245
+ "timestamp": datetime.now().isoformat(),
1246
+ "changes_count": len(changes),
1247
+ "changes": changes,
1248
+ "schedule": clean_new,
1249
+ "status": "PENDING"
1250
+ }
1251
+ save_proposal(proposal)
1252
+ return {"status": "SUCCESS", "message": "Proposal submitted.", "proposal": proposal}
1253
+
1254
+ @app.post("/schedule/proposals/{proposal_id}/approve")
1255
+ def approve_proposal(proposal_id: str):
1256
+ from storage import load_proposals, delete_proposal
1257
+ proposals = load_proposals()
1258
+ target = next((p for p in proposals if p.get("id") == proposal_id), None)
1259
+ if not target:
1260
+ raise HTTPException(404, "Proposal not found.")
1261
+
1262
+ clean_new = target["schedule"]
1263
+ save_schedule(clean_new)
1264
+ if target.get("changes"):
1265
+ add_history_entry(
1266
+ operation_type="PROPOSAL_APPROVED",
1267
+ description=f"Approved changes by {target.get('proposer_name')} ({len(target['changes'])} cells affected)",
1268
+ affected_sections=[],
1269
+ changes=target["changes"],
1270
+ status="SUCCESS",
1271
+ constraints=[]
1272
+ )
1273
+
1274
+ delete_proposal(proposal_id)
1275
+ return {"status": "SUCCESS", "message": "Proposal approved and applied."}
1276
+
1277
+ @app.delete("/schedule/proposals/{proposal_id}")
1278
+ def reject_proposal(proposal_id: str):
1279
+ from storage import delete_proposal
1280
+ delete_proposal(proposal_id)
1281
+ return {"status": "SUCCESS", "message": "Proposal rejected."}
1282
+
1283
+
1284
+ # ═════════════════════════════════════════════════════════════════════════════
1285
+ # INJECT SUBJECT (ADD TO TIMETABLE)
1286
+ # ═════════════════════════════════════════════════════════════════════════════
1287
+
1288
+ @app.post("/schedule/inject")
1289
+ def inject_subject(req: InjectRequest):
1290
+ """Insert one or more new class entries into the current schedule."""
1291
+ if not schedule_exists():
1292
+ raise HTTPException(400, "No timetable generated yet. Call POST /generate first.")
1293
+
1294
+ if not req.entries:
1295
+ raise HTTPException(400, "No entries provided.")
1296
+
1297
+ data = load_data()
1298
+ sched_data = load_schedule()
1299
+ current = sched_data["schedule"]
1300
+ old_schedule = _clean(dict(current)) # snapshot before mutation
1301
+
1302
+ # Build a set of existing task IDs to avoid collisions
1303
+ existing_ids = set(current.keys())
1304
+
1305
+ # Build a lookup of faculty_name -> faculty for room assignment
1306
+ rooms = data.get("rooms", [])
1307
+ subjects_lookup = {s["code"].lower(): s for s in data.get("subjects", [])}
1308
+
1309
+ injected_entries = []
1310
+
1311
+ for entry in req.entries:
1312
+ # Check for section collisions first
1313
+ for t_id, info in current.items():
1314
+ if info.get("section_id") == entry.section_id and info.get("day_index") == entry.day_index:
1315
+ c_start = info.get("period_index", 0)
1316
+ c_dur = info.get("duration", 1)
1317
+ c_end = c_start + c_dur
1318
+
1319
+ e_start = entry.period_index
1320
+ e_dur = entry.duration
1321
+ e_end = e_start + e_dur
1322
+
1323
+ if max(c_start, e_start) < min(c_end, e_end):
1324
+ raise HTTPException(400, f"Section {entry.section_id} already has a class scheduled at {const.DAYS[entry.day_index]} Period {e_start + 1}.")
1325
+
1326
+ # Determine room: use provided room_id, or auto-pick first available
1327
+ room_id = entry.room_id or ""
1328
+ room_name = ""
1329
+ if room_id:
1330
+ for r in rooms:
1331
+ if r["id"] == room_id:
1332
+ room_name = f"{r['id']} ({'Lab' if r.get('is_lab') else r.get('building', 'Main')})"
1333
+ break
1334
+ elif rooms:
1335
+ # Auto-assign: pick a room not occupied at this slot
1336
+ occupied_rooms = set()
1337
+ for info in current.values():
1338
+ if info.get("day_index") == entry.day_index and info.get("period_index") == entry.period_index:
1339
+ occupied_rooms.add(info.get("room_id", ""))
1340
+ # For multi-period blocks, also check period_index + 1
1341
+ dur = info.get("duration", 1)
1342
+ if dur > 1:
1343
+ for di in range(dur):
1344
+ if info.get("day_index") == entry.day_index and info.get("period_index") + di == entry.period_index:
1345
+ occupied_rooms.add(info.get("room_id", ""))
1346
+
1347
+ # Prefer labs for lab subjects, regular rooms for theory
1348
+ sub_info = subjects_lookup.get(entry.subject_code.lower(), {})
1349
+ is_lab_subject = sub_info.get("type", "THEORY").upper() == "LAB"
1350
+
1351
+ for r in rooms:
1352
+ if r["id"] not in occupied_rooms:
1353
+ if is_lab_subject and r.get("is_lab"):
1354
+ room_id = r["id"]
1355
+ room_name = f"{r['id']} (Lab)"
1356
+ break
1357
+ elif not is_lab_subject and not r.get("is_lab"):
1358
+ room_id = r["id"]
1359
+ room_name = f"{r['id']} ({r.get('building', 'Main')})"
1360
+ break
1361
+ # Fallback: just pick the first available
1362
+ if not room_id:
1363
+ for r in rooms:
1364
+ if r["id"] not in occupied_rooms:
1365
+ room_id = r["id"]
1366
+ room_name = f"{r['id']} ({r.get('building', 'Main')})"
1367
+ break
1368
+
1369
+ # Generate a unique task ID
1370
+ base_id = f"{entry.subject_code.lower()}-{entry.section_id.lower()}-INJECT"
1371
+ task_id = base_id
1372
+ counter = 0
1373
+ while task_id in existing_ids:
1374
+ counter += 1
1375
+ task_id = f"{base_id}-{counter}"
1376
+ existing_ids.add(task_id)
1377
+
1378
+ # Compute start_slot for compatibility
1379
+ start_slot = entry.day_index * const.NUM_TEACHING_SLOTS_PER_DAY + entry.period_index
1380
+
1381
+ schedule_entry = {
1382
+ "start_slot": start_slot,
1383
+ "day_index": entry.day_index,
1384
+ "day_name": const.DAYS[entry.day_index] if entry.day_index < len(const.DAYS) else f"Day{entry.day_index}",
1385
+ "period_index": entry.period_index,
1386
+ "room_id": room_id,
1387
+ "room_name": room_name,
1388
+ "faculty_name": entry.faculty_name,
1389
+ "subject_code": entry.subject_code.lower(),
1390
+ "section_id": entry.section_id,
1391
+ "duration": entry.duration,
1392
+ }
1393
+
1394
+ current[task_id] = schedule_entry
1395
+ injected_entries.append({"task_id": task_id, **schedule_entry})
1396
+
1397
+ # Save updated schedule
1398
+ save_schedule(current)
1399
+ # Log history
1400
+ clean = _clean(current)
1401
+ changes, affected = diff_schedules(old_schedule, clean)
1402
+ if changes:
1403
+ add_history_entry(
1404
+ operation_type="INJECT_SUBJECT",
1405
+ description=f"Injected {len(injected_entries)} class(es) (e.g. {injected_entries[0].get('subject_code') if injected_entries else 'subject'})",
1406
+ affected_sections=affected,
1407
+ changes=changes,
1408
+ status="SUCCESS",
1409
+ constraints=[]
1410
+ )
1411
+
1412
+ return {
1413
+ "status": "SUCCESS",
1414
+ "message": f"Injected {len(injected_entries)} class(es) into the timetable.",
1415
+ "injected": injected_entries,
1416
+ "schedule": clean,
1417
+ "grid": _build_grid(clean),
1418
+ **_timetable_constants(),
1419
+ }
1420
+
1421
+ @app.post("/schedule/remove")
1422
+ def remove_class(req: RemoveRequest):
1423
+ """Remove a specific class from the timetable manually."""
1424
+ if not schedule_exists():
1425
+ raise HTTPException(400, "No timetable generated yet.")
1426
+
1427
+ sched_data = load_schedule()
1428
+ current = sched_data["schedule"]
1429
+ old_schedule = _clean(dict(current)) # snapshot before mutation
1430
+
1431
+ if req.task_id not in current:
1432
+ raise HTTPException(404, f"Class with ID {req.task_id} not found.")
1433
+
1434
+ removed_entry = current.pop(req.task_id)
1435
+
1436
+ # Save updated schedule
1437
+ save_schedule(current)
1438
+ clean = _clean(current)
1439
+ changes, affected = diff_schedules(old_schedule, clean)
1440
+ if changes:
1441
+ add_history_entry(
1442
+ operation_type="REMOVE_SUBJECT",
1443
+ description=f"Removed class: {removed_entry.get('subject_code')} from {removed_entry.get('section_id')}",
1444
+ affected_sections=affected,
1445
+ changes=changes,
1446
+ status="SUCCESS",
1447
+ constraints=[]
1448
+ )
1449
+
1450
+ clean = _clean(current)
1451
+ return {
1452
+ "status": "SUCCESS",
1453
+ "message": "Class removed successfully.",
1454
+ "removed": req.task_id,
1455
+ "schedule": clean,
1456
+ "grid": _build_grid(clean),
1457
+ **_timetable_constants(),
1458
+ }
1459
+
1460
+
1461
+ @app.get("/schedule/free-teachers")
1462
+ def get_free_teachers(day_index: int, period_index: int):
1463
+ """Return teachers who have NO class at the given (day, period) slot."""
1464
+ if not schedule_exists():
1465
+ raise HTTPException(400, "No timetable generated yet.")
1466
+
1467
+ data = load_data()
1468
+ sched_data = load_schedule()
1469
+ current = sched_data["schedule"]
1470
+ faculties = data.get("faculties", [])
1471
+
1472
+ # Find all faculty names busy at this slot
1473
+ busy_names = set()
1474
+ for info in current.values():
1475
+ d = info.get("day_index")
1476
+ p = info.get("period_index")
1477
+ dur = info.get("duration", 1)
1478
+ if d == day_index:
1479
+ for i in range(dur):
1480
+ if p + i == period_index:
1481
+ busy_names.add(info.get("faculty_name", "").strip().lower())
1482
+
1483
+ # Return faculties NOT busy
1484
+ free = []
1485
+ busy = []
1486
+ for fac in faculties:
1487
+ fac_name = fac.get("name", "").strip()
1488
+ if fac_name.lower() in busy_names:
1489
+ busy.append({
1490
+ "id": fac["id"],
1491
+ "name": fac_name,
1492
+ "designation": fac.get("designation", ""),
1493
+ "max_hours": fac.get("max_hours", 18),
1494
+ "status": "busy",
1495
+ })
1496
+ else:
1497
+ free.append({
1498
+ "id": fac["id"],
1499
+ "name": fac_name,
1500
+ "designation": fac.get("designation", ""),
1501
+ "max_hours": fac.get("max_hours", 18),
1502
+ "status": "free",
1503
+ })
1504
+
1505
+ return {
1506
+ "day_index": day_index,
1507
+ "period_index": period_index,
1508
+ "free_count": len(free),
1509
+ "busy_count": len(busy),
1510
+ "free_teachers": free,
1511
+ "busy_teachers": busy,
1512
+ }
1513
+
1514
+
1515
+ # ═════════════════════════════════════════════════════════════════════════════
1516
+ # UPDATE TIMETABLE
1517
+ # ═════════════════════════════════════════════════════════════════════════════
1518
+
1519
+ _PRIORITY_KEYWORDS = [
1520
+ "replace", "substitute", "take over", "will take", "on leave", "cover",
1521
+ "permanently", "change faculty", "hand over", "assign all",
1522
+ "cancel", "no class", "holiday", "off day",
1523
+ "no toc", "no nlp", "no ml", "no cn", "no sepm", "no nosql",
1524
+ "move", "shift", "reschedule", "transfer", "relocate",
1525
+ "change room", "to lab", "to room", "assign room",
1526
+ "extra class", "makeup", "compensatory", "schedule extra",
1527
+ "swap", "exchange", "freeze", "lock slot",
1528
+ "should not be free", "must not be free", "cannot be free",
1529
+ "must have a class", "should have a class", "no free period",
1530
+ "first hour", "first period", "last period", "last hour",
1531
+ ]
1532
+
1533
+ @app.post("/update")
1534
+ def update(req: UpdateRequest):
1535
+ if not schedule_exists():
1536
+ raise HTTPException(400, "No timetable generated yet. Call POST /generate first.")
1537
+
1538
+ prompt = req.prompt.strip()
1539
+ if not prompt:
1540
+ raise HTTPException(400, "Prompt cannot be empty.")
1541
+
1542
+ try:
1543
+ data = load_data()
1544
+ constraints = []
1545
+
1546
+ use_local = any(kw in prompt.lower() for kw in _PRIORITY_KEYWORDS)
1547
+ if use_local:
1548
+ local = smart_parse(prompt, data["faculties"],
1549
+ data.get("subjects", []), data.get("sections", []))
1550
+ if local:
1551
+ constraints = [local]
1552
+
1553
+ if not constraints:
1554
+ result = get_constraint(prompt)
1555
+ if not result.get("success"):
1556
+ raise HTTPException(400, result.get("error", "Constraint parse failed."))
1557
+ constraints = result.get("constraints", [])
1558
+
1559
+ if not constraints:
1560
+ raise HTTPException(422, "Could not parse any constraint from the instruction.")
1561
+
1562
+ if req.preview_only:
1563
+ return {
1564
+ "preview": True,
1565
+ "parsed_constraints": constraints,
1566
+ "constraint_type": constraints[0].get("type", "?"),
1567
+ }
1568
+
1569
+ sched = load_schedule()
1570
+ current_solution = sched["schedule"]
1571
+ previous_schedule_clean = _clean(current_solution)
1572
+
1573
+ facs, subs, secs, rooms, allocs = _build_objects(data)
1574
+ tasks = prepare_scheduling_tasks(allocs, facs, subs, secs)
1575
+ tasks_by_id = {t.task_id: t for t in tasks}
1576
+
1577
+ for tid, info in current_solution.items():
1578
+ if tid in tasks_by_id:
1579
+ info["task_obj"] = tasks_by_id[tid]
1580
+
1581
+ final_solution = current_solution
1582
+ all_changes = []
1583
+
1584
+ for constraint in constraints:
1585
+ optimizer = PartialOptimizer(tasks, facs, secs, rooms, final_solution)
1586
+ op_status, new_solution, _, summary = \
1587
+ optimizer.apply_constraint_and_reoptimize(constraint)
1588
+
1589
+ if op_status in ("OPTIMAL", "FEASIBLE", "NO_CHANGE"):
1590
+ final_solution = new_solution
1591
+ all_changes.append(summary)
1592
+ else:
1593
+ all_changes.append(f"⚠️ {summary}")
1594
+
1595
+ if req.propose_only:
1596
+ # Compute but don't save — return schedule for the frontend to propose
1597
+ clean = _clean(final_solution)
1598
+ return {
1599
+ "status": "PROPOSED",
1600
+ "parsed_constraints": constraints,
1601
+ "changes": all_changes,
1602
+ "schedule": clean,
1603
+ }
1604
+
1605
+ save_schedule(final_solution)
1606
+ clean = _clean(final_solution)
1607
+ changes, affected = diff_schedules(previous_schedule_clean, clean)
1608
+
1609
+ add_history_entry(
1610
+ operation_type="SLM_UPDATE",
1611
+ description=f"AI Update: {prompt}",
1612
+ affected_sections=affected,
1613
+ changes=changes,
1614
+ status="SUCCESS",
1615
+ constraints=constraints
1616
+ )
1617
+
1618
+ clean = _clean(final_solution)
1619
+ return {
1620
+ "status": "SUCCESS",
1621
+ "parsed_constraints": constraints,
1622
+ "constraint_type": constraints[0].get("type", "?"),
1623
+ "changes": all_changes,
1624
+ "previous_schedule": previous_schedule_clean,
1625
+ "updated_schedule": clean,
1626
+ "grid": _build_grid(clean),
1627
+ **_timetable_constants(),
1628
+ }
1629
+
1630
+ except HTTPException:
1631
+ raise
1632
+ except Exception:
1633
+ raise HTTPException(500, traceback.format_exc())
1634
+
1635
+
1636
+ # ═════════════════════════════════════════════════════════════════════════════
1637
+ # LEAVES & SUBSTITUTIONS
1638
+ # ═════════════════════════════════════════════════════════════════════════════
1639
+
1640
+ class LeaveRequestIn(BaseModel):
1641
+ faculty_id: str
1642
+ days: list[str]
1643
+ reason: str
1644
+
1645
+ @app.get("/leave")
1646
+ def get_leaves():
1647
+ return {"leaves": load_leave_requests()}
1648
+
1649
+ @app.post("/leave", status_code=201)
1650
+ def create_leave(req: LeaveRequestIn):
1651
+ leaves = load_leave_requests()
1652
+ leave_id = str(uuid.uuid4())
1653
+ new_leave = {
1654
+ "leave_id": leave_id,
1655
+ "faculty_id": req.faculty_id,
1656
+ "days": req.days,
1657
+ "reason": req.reason,
1658
+ "status": "PENDING"
1659
+ }
1660
+ leaves.append(new_leave)
1661
+ save_leave_requests(leaves)
1662
+ return {"message": "Leave request created", "leave": new_leave}
1663
+
1664
+ @app.post("/leave/approve/{leave_id}")
1665
+ def approve_leave(leave_id: str):
1666
+ leaves = load_leave_requests()
1667
+ target_leave: dict | None = next((l for l in leaves if l.get("leave_id") == leave_id), None)
1668
+
1669
+ if not target_leave:
1670
+ raise HTTPException(404, "Leave request not found")
1671
+
1672
+ if target_leave.get("status") != "PENDING":
1673
+ raise HTTPException(400, f"Leave is already {target_leave.get('status')}")
1674
+
1675
+ target_leave["status"] = "APPROVED"
1676
+ save_leave_requests(leaves)
1677
+
1678
+ # Convert dict to namedtuple or dataclass instance expected by engine
1679
+ from models import LeaveRequest as LRModel
1680
+ lr_obj = LRModel(**target_leave)
1681
+
1682
+ # Trigger substitution finder
1683
+ process_leave_approval(lr_obj)
1684
+
1685
+ return {"message": "Leave approved and substitution process started."}
1686
+
1687
+ @app.post("/leave/reject/{leave_id}")
1688
+ def reject_leave(leave_id: str):
1689
+ leaves = load_leave_requests()
1690
+ target = next((l for l in leaves if l["leave_id"] == leave_id), None)
1691
+ if not target: raise HTTPException(404, "Leave request not found")
1692
+ target["status"] = "REJECTED"
1693
+ save_leave_requests(leaves)
1694
+ return {"message": "Leave request rejected."}
1695
+
1696
+ @app.get("/substitution")
1697
+ @app.get("/substitution/pending")
1698
+ def get_pending_substitutions(faculty_id: str | None = None):
1699
+ check_timeouts()
1700
+ reqs = load_substitution_requests()
1701
+
1702
+ if faculty_id:
1703
+ reqs = [r for r in reqs if r["candidate_faculty_id"] == faculty_id and r["status"] == "PENDING"]
1704
+ else:
1705
+ reqs = [r for r in reqs if r["status"] == "PENDING"]
1706
+
1707
+ return {"substitutions": reqs}
1708
+
1709
+ @app.post("/substitution/{request_id}/accept")
1710
+ def accept_substitution(request_id: str):
1711
+ success, msg = handle_acceptance(request_id)
1712
+ if not success:
1713
+ raise HTTPException(400, msg)
1714
+ return {"message": msg}
1715
+
1716
+ @app.post("/substitution/{request_id}/decline")
1717
+ def decline_substitution(request_id: str):
1718
+ success, msg = handle_decline(request_id)
1719
+ if not success:
1720
+ raise HTTPException(400, msg)
1721
+ return {"message": msg}
1722
+
1723
+ @app.get("/substitution/unresolved")
1724
+ def get_unresolved_substitutions():
1725
+ check_timeouts()
1726
+ # A slot is unresolved if all requests for it are DECLINED/TIMEOUT, and no ACCEPTED exists
1727
+ # Or if no requests were generated at all (handled separately or indicated by lack of requests)
1728
+ reqs = load_substitution_requests()
1729
+ leaves = load_leave_requests()
1730
+
1731
+ unresolved_slots = []
1732
+ # simplified logic: find slots where all reqs are not PENDING/ACCEPTED
1733
+ # A true implementation would group by leave_id + slot
1734
+
1735
+ return {"unresolved": unresolved_slots, "message": "Not fully implemented for MVP"}
1736
+
1737
+ # ═════════════════════════════════════════════════════════════════════════════
1738
+ # CANCELLATION REQUESTS
1739
+ # ═════════════════════════════════════════════════════════════════════════════
1740
+
1741
+ class CancellationRequestIn(BaseModel):
1742
+ section_id: str
1743
+ day: str
1744
+ period: int
1745
+ subject: str
1746
+ reason: str
1747
+ faculty_id: str
1748
+
1749
+ @app.post("/cancellations/request", status_code=201)
1750
+ def create_cancellation_request(req: CancellationRequestIn):
1751
+ cancellations = load_cancellations()
1752
+ cancel_id = str(uuid.uuid4())
1753
+ new_cancel = {
1754
+ "id": cancel_id,
1755
+ "section_id": req.section_id,
1756
+ "day": req.day,
1757
+ "period": req.period,
1758
+ "subject": req.subject,
1759
+ "reason": req.reason,
1760
+ "faculty_id": req.faculty_id,
1761
+ "status": "PENDING",
1762
+ "created_at": datetime.now().isoformat()
1763
+ }
1764
+ cancellations.append(new_cancel)
1765
+ save_cancellations(cancellations)
1766
+ return {"message": "Cancellation request submitted", "cancellation": new_cancel}
1767
+
1768
+ @app.get("/cancellations")
1769
+ def get_cancellations():
1770
+ return {"cancellations": load_cancellations()}
1771
+
1772
+ @app.post("/cancellations/{cancel_id}/status")
1773
+ def update_cancellation_status(cancel_id: str, payload: dict = Body(...)):
1774
+ status = payload.get("status")
1775
+ if not status:
1776
+ raise HTTPException(400, "Missing status")
1777
+
1778
+ cancellations = load_cancellations()
1779
+ target = next((c for c in cancellations if c["id"] == cancel_id), None)
1780
+ if not target:
1781
+ raise HTTPException(404, "Cancellation not found")
1782
+
1783
+ target["status"] = status
1784
+ save_cancellations(cancellations)
1785
+ return {"message": f"Cancellation marked as {status}", "cancellation": target}
1786
+
1787
+ # ═════════════════════════════════════════════════════════════════════════════
1788
+ # CHANGE HISTORY
1789
+ # ═════════════════════════════════════════════════════════════════════════════
1790
+
1791
+ @app.get("/history")
1792
+ def get_history():
1793
+ history = load_history()
1794
+ return {
1795
+ "count": len(history),
1796
+ "history": list(reversed(history)),
1797
+ }
1798
+
1799
+ @app.post("/history/revert/{history_id}")
1800
+ def revert_history(history_id: str, force: bool = False):
1801
+ history = load_history()
1802
+ entry = next((e for e in history if e.get("id") == history_id), None)
1803
+ if not entry:
1804
+ raise HTTPException(404, "History entry not found.")
1805
+
1806
+ sched_raw = load_schedule() or {}
1807
+ current_sched = sched_raw.get("schedule", {}) if isinstance(sched_raw, dict) else {}
1808
+ changes = entry.get("changes", [])
1809
+
1810
+ if not force:
1811
+ # Check for conflicts
1812
+ conflicts = []
1813
+ for c in changes:
1814
+ tid = c["task_id"]
1815
+ curr_val = _clean({tid: current_sched[tid]}).get(tid) if tid in current_sched else None
1816
+ after_val = c["after"]
1817
+ if curr_val != after_val:
1818
+ conflicts.append(tid)
1819
+
1820
+ if conflicts:
1821
+ raise HTTPException(409, {
1822
+ "message": "Conflict detected: The timetable has been modified since this change was made. Reverting will overwrite those newer modifications. Do you want to force revert?",
1823
+ "conflicts": conflicts
1824
+ })
1825
+
1826
+ # Apply revert
1827
+ new_sched = dict(current_sched)
1828
+ for c in changes:
1829
+ tid = c["task_id"]
1830
+ before_val = c["before"]
1831
+ if before_val is None:
1832
+ if tid in new_sched:
1833
+ del new_sched[tid]
1834
+ else:
1835
+ new_sched[tid] = dict(before_val)
1836
+
1837
+ clean_new = _clean(new_sched)
1838
+ save_schedule(clean_new)
1839
+
1840
+ # Mark the original entry as REVERTED (keep it visible)
1841
+ entry["status"] = "REVERTED"
1842
+ save_history(history)
1843
+
1844
+ # Log the revert itself as a new entry
1845
+ rev_changes, rev_affected = diff_schedules(_clean(current_sched), clean_new)
1846
+ if rev_changes:
1847
+ add_history_entry(
1848
+ operation_type="REVERT",
1849
+ description=f"Reverted: {entry.get('description', history_id)}",
1850
+ affected_sections=rev_affected,
1851
+ changes=rev_changes,
1852
+ status="SUCCESS"
1853
+ )
1854
+
1855
+ return {
1856
+ "status": "SUCCESS",
1857
+ "message": "Revert successful.",
1858
+ "schedule": clean_new,
1859
+ "grid": _build_grid(clean_new),
1860
+ **_timetable_constants(),
1861
+ }
1862
+
1863
+ @app.delete("/history")
1864
+ def delete_history():
1865
+ clear_history()
1866
+ return {"message": "History cleared."}
app.py ADDED
@@ -0,0 +1,639 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # app.py
2
+
3
+ import streamlit as st
4
+ import pandas as pd
5
+ from collections import defaultdict
6
+ from datetime import datetime
7
+
8
+ from storage import (load_data, save_data, save_schedule, load_schedule,
9
+ schedule_exists, clear_schedule, load_history,
10
+ add_history_entry, clear_history,
11
+ save_original_schedule, load_original_schedule,
12
+ original_schedule_exists, clear_original_schedule)
13
+ from models import Faculty, Subject, Section, Room, SubjectType
14
+ from data_loader import Allocation, prepare_scheduling_tasks
15
+ from solver import TimetableSolver
16
+ from slm_inference import get_constraint, check_api_health
17
+ from partial_optimizer import PartialOptimizer
18
+ import constants as const
19
+
20
+ st.set_page_config(page_title="VTU Timetable Generator", layout="wide")
21
+
22
+ # ── Helpers ───────────────────────────────────────────────────────────────
23
+ def get_subject_type_enum(type_str):
24
+ return {
25
+ "THEORY": SubjectType.THEORY, "LAB": SubjectType.LAB,
26
+ "SOFTSKILL": SubjectType.SOFTSKILL, "FORUM": SubjectType.FORUM
27
+ }.get(type_str.upper(), SubjectType.THEORY)
28
+
29
+ def convert_json_to_objects(data):
30
+ fac_objs = [Faculty(f['id'], f['name'], f['designation'], f['max_hours'])
31
+ for f in data['faculties']]
32
+ sub_objs = [Subject(s['code'], s['name'], s['credits'],
33
+ get_subject_type_enum(s['type']),
34
+ s.get('is_core', True), s.get('is_heavy', False))
35
+ for s in data['subjects']]
36
+ sec_objs = [Section(s['id'], s['semester'], s['strength'])
37
+ for s in data['sections']]
38
+ room_objs = [Room(r['id'], r['capacity'], r['is_lab'], r['building'])
39
+ for r in data['rooms']]
40
+ alloc_objs = [Allocation(a['faculty_id'], a['subject_code'],
41
+ a['section_id'], a.get('elective_group'))
42
+ for a in data['allocations']]
43
+ return fac_objs, sub_objs, sec_objs, room_objs, alloc_objs
44
+
45
+ def rebuild_objects(data):
46
+ """Rebuild all domain objects from stored data."""
47
+ return convert_json_to_objects(data)
48
+
49
+ def render_timetable_html(solution, sections):
50
+ """Render the timetable as an HTML table."""
51
+ parent_sections = sorted(list(set(
52
+ s.section_id.split('-')[0].upper() for s in sections)))
53
+
54
+ # Grid stores list of (subject_code, faculty_name) per slot
55
+ merged_grid = defaultdict(lambda: defaultdict(lambda: defaultdict(list)))
56
+ for task_id, info in solution.items():
57
+ sec_id = info.get('section_id', '')
58
+ parent_sec = sec_id.split('-')[0].upper()
59
+ day = info.get('day_index', 0)
60
+ period = info.get('period_index', 0)
61
+ dur = info.get('duration', 1)
62
+ subject = info.get('subject_code', '?').upper()
63
+ faculty = info.get('faculty_name', '')
64
+ short_fac = (faculty.replace('Prof. ','').replace('Dr. ','')
65
+ .replace('Mr. ','').replace('Ms. ',''))
66
+ for i in range(dur):
67
+ entry = (subject, short_fac)
68
+ if entry not in merged_grid[parent_sec][day][period + i]:
69
+ merged_grid[parent_sec][day][period + i].append(entry)
70
+
71
+ st.markdown("""
72
+ <style>
73
+ table.tt { width:100%; border-collapse:collapse; font-size:0.82em; }
74
+ table.tt th, table.tt td {
75
+ border:2px solid #555; padding:5px 4px;
76
+ text-align:center; vertical-align:middle; min-width:80px;
77
+ }
78
+ table.tt th { background:#2b2b2b; color:#fff; font-weight:bold; }
79
+ .vt { writing-mode:vertical-rl; transform:rotate(180deg);
80
+ font-weight:bold; background:#1e1e1e; color:#ccc; }
81
+ .dh { font-weight:bold; background:#1a1a2e; color:#fff; }
82
+ .subj { font-weight:bold; font-size:0.95em; color:#4fc3f7; }
83
+ .fac { font-size:0.78em; color:#aaa; margin-top:2px; }
84
+ .multi-subj { border-top:1px dashed #555; margin-top:3px; padding-top:3px; }
85
+ </style>""", unsafe_allow_html=True)
86
+
87
+ for p_sec in parent_sections:
88
+ st.markdown(f"### Section: {p_sec}")
89
+ html = '<table class="tt"><tr><th>Day \\ Time</th>'
90
+ for h in const.TIMETABLE_HEADERS:
91
+ html += f'<th>{h}</th>'
92
+ html += '</tr>'
93
+
94
+ # Detect all days that have classes (including Saturday from extra classes)
95
+ days_in_solution = set()
96
+ for task_id, info in solution.items():
97
+ sec = info.get('section_id','')
98
+ if sec.split('-')[0].upper() == p_sec or sec.upper() == p_sec:
99
+ days_in_solution.add(info['day_index'])
100
+
101
+ # Always show Mon-Fri; add Saturday only if it has classes
102
+ all_day_indices = list(range(len(const.DAYS)))
103
+ sat_index = 5 # Saturday index
104
+ if sat_index in days_in_solution and sat_index not in all_day_indices:
105
+ all_day_indices.append(sat_index)
106
+
107
+ # Day name lookup including Saturday
108
+ all_day_names = list(const.DAYS) + (['SAT'] if len(const.DAYS) <= 5 else [])
109
+
110
+ total_days = len(all_day_indices)
111
+
112
+ for row_num, day_idx in enumerate(all_day_indices):
113
+ day_name = all_day_names[day_idx] if day_idx < len(all_day_names) else f'Day{day_idx}'
114
+ html += f'<tr><td class="dh">{day_name}</td>'
115
+ period_counter = 0
116
+ for header_text in const.TIMETABLE_HEADERS:
117
+ is_break = (header_text == "10:35-10:50")
118
+ is_lunch = (header_text == "12:40-1:40")
119
+ if is_break:
120
+ if row_num == 0:
121
+ html += f'<td rowspan="{total_days}" class="vt">Tea Break</td>'
122
+ elif is_lunch:
123
+ if row_num == 0:
124
+ html += f'<td rowspan="{total_days}" class="vt">Lunch Break</td>'
125
+ else:
126
+ entries = merged_grid[p_sec][day_idx].get(period_counter, [])
127
+ if entries:
128
+ cell = ''
129
+ for idx, (subj, fac) in enumerate(entries):
130
+ div_cls = 'multi-subj' if idx > 0 else ''
131
+ cell += (f'<div class="{div_cls}">'
132
+ f'<div class="subj">{subj}</div>'
133
+ f'<div class="fac">{fac}</div>'
134
+ f'</div>')
135
+ html += f'<td>{cell}</td>'
136
+ else:
137
+ html += '<td></td>'
138
+ period_counter += 1
139
+ html += '</tr>'
140
+ html += '</table><br>'
141
+ st.markdown(html, unsafe_allow_html=True)
142
+
143
+ # ── Sidebar ───────────────────────────────────────────────────────────────
144
+ st.sidebar.title("🎓 VTU Timetable System")
145
+
146
+ # Show schedule status in sidebar
147
+ if schedule_exists():
148
+ sched = load_schedule()
149
+ gen_at = sched.get('generated_at', '')[:16].replace('T', ' ')
150
+ st.sidebar.success(f"📅 Schedule active\nGenerated: {gen_at}")
151
+ else:
152
+ st.sidebar.warning("No schedule generated yet")
153
+
154
+ page = st.sidebar.radio("Navigate", [
155
+ "🗓️ Generate Timetable",
156
+ "✏️ Update Timetable",
157
+ "📊 Original vs Current",
158
+ "📋 Change History",
159
+ "👥 Manage Faculties",
160
+ "📚 Manage Subjects",
161
+ "🏛️ Manage Sections",
162
+ "🚪 Manage Rooms",
163
+ "🔗 Manage Allocations",
164
+ ])
165
+
166
+ data = load_data()
167
+
168
+ # ═════════════════════════════════════════════════════════════════════════════
169
+ # PAGE: GENERATE TIMETABLE
170
+ # ═════════════════════════════════════════════════════════════════════════════
171
+ if page == "🗓️ Generate Timetable":
172
+ st.header("🗓️ Generate Semester Timetable")
173
+
174
+ if schedule_exists():
175
+ st.warning("⚠️ A timetable is already active for this semester.")
176
+ col1, col2 = st.columns(2)
177
+ with col1:
178
+ if st.button("📄 View Current Timetable"):
179
+ st.session_state['show_current'] = True
180
+ with col2:
181
+ if st.button("🔄 Generate New Timetable (replaces current)", type="secondary"):
182
+ clear_schedule()
183
+ clear_history()
184
+ clear_original_schedule()
185
+ st.rerun()
186
+
187
+ if st.session_state.get('show_current'):
188
+ sched = load_schedule()
189
+ if sched:
190
+ solution = sched['schedule']
191
+ _, _, secs, _, _ = rebuild_objects(data)
192
+ render_timetable_html(solution, secs)
193
+
194
+ else:
195
+ st.info("Generate the timetable once — it will be fixed for the semester. "
196
+ "Use 'Update Timetable' to make changes later via prompts.")
197
+
198
+ time_limit = st.slider("Solver time limit (seconds)", 10, 240, 120)
199
+
200
+ st.markdown("### 📝 Add Constraints Before Generating (Optional)")
201
+ st.caption("These rules will be baked into the timetable from the start.")
202
+
203
+ # Constraint input area
204
+ if 'pre_constraints' not in st.session_state:
205
+ st.session_state['pre_constraints'] = []
206
+
207
+ col1, col2 = st.columns([4, 1])
208
+ with col1:
209
+ new_prompt = st.text_input(
210
+ "Type a constraint:",
211
+ placeholder="e.g. Prof. Anu is not available on Friday",
212
+ key="pre_constraint_input"
213
+ )
214
+ with col2:
215
+ st.markdown("<br>", unsafe_allow_html=True)
216
+ if st.button("➕ Add") and new_prompt.strip():
217
+ st.session_state['pre_constraints'].append(new_prompt.strip())
218
+ st.rerun()
219
+
220
+ # Show added constraints
221
+ if st.session_state['pre_constraints']:
222
+ st.markdown("**Constraints to apply:**")
223
+ for i, c in enumerate(st.session_state['pre_constraints']):
224
+ col1, col2 = st.columns([5, 1])
225
+ col1.markdown(f"• {c}")
226
+ if col2.button("❌", key=f"del_{i}"):
227
+ st.session_state['pre_constraints'].pop(i)
228
+ st.rerun()
229
+ else:
230
+ st.info("No constraints added — timetable will be generated with default rules only.")
231
+
232
+ st.divider()
233
+
234
+ if st.button("🚀 Generate Timetable", type="primary"):
235
+ try:
236
+ facs, subs, secs, rooms, allocs = convert_json_to_objects(data)
237
+ tasks = prepare_scheduling_tasks(allocs, facs, subs, secs)
238
+ st.write(f"Scheduling {len(tasks)} tasks...")
239
+
240
+ # Convert pre-constraints via SLM
241
+ pre_slm_constraints = []
242
+ if st.session_state.get('pre_constraints'):
243
+ with st.spinner("Converting constraints via SLM API..."):
244
+ from slm_inference import get_constraints_batch
245
+ pre_slm_constraints = get_constraints_batch(
246
+ st.session_state['pre_constraints'])
247
+ st.write(f"✅ {len(pre_slm_constraints)} constraint(s) parsed")
248
+
249
+ solver = TimetableSolver(tasks, facs, secs, rooms)
250
+ with st.spinner("Optimizing schedule..."):
251
+ status, solution = solver.solve(
252
+ time_limit_seconds=time_limit,
253
+ enable_soft_constraints=True,
254
+ slm_constraints=pre_slm_constraints)
255
+
256
+ if status in ("OPTIMAL", "FEASIBLE"):
257
+ save_schedule(solution)
258
+ save_original_schedule(solution) # permanent snapshot
259
+ st.success(f"✅ Timetable Generated! Status: {status}")
260
+ st.balloons()
261
+ render_timetable_html(solution, secs)
262
+ else:
263
+ st.error(f"❌ Solver failed: {status}")
264
+ except Exception as e:
265
+ st.error(f"Error: {e}")
266
+ import traceback; st.code(traceback.format_exc())
267
+
268
+ # ═════════════════════════════════════════════════════════════════════════════
269
+ # PAGE: UPDATE TIMETABLE
270
+ # ═════════════════════════════════════════════════════════════════════════════
271
+ elif page == "✏️ Update Timetable":
272
+ st.header("✏️ Update Timetable with Natural Language")
273
+
274
+ if not schedule_exists():
275
+ st.error("❌ No timetable generated yet. Go to 'Generate Timetable' first.")
276
+ st.stop()
277
+
278
+ # API Health Check
279
+ with st.spinner("Checking SLM API..."):
280
+ api_ok = check_api_health()
281
+ if api_ok:
282
+ st.success("✅ SLM API is online")
283
+ else:
284
+ st.error("❌ SLM API is offline. Check HuggingFace Space.")
285
+ st.stop()
286
+
287
+ st.markdown("""
288
+ **How it works:** Type a natural language instruction. Only the affected
289
+ slots will be rescheduled — the rest of the timetable stays unchanged.
290
+
291
+ **Examples:**
292
+ - `Prof. Anu is not available on Friday`
293
+ - `NLP Lab must be in consecutive slots`
294
+ - `ML should be scheduled in the morning`
295
+ - `Slot 5 is the lunch break`
296
+ - `Limit Sanjay to 3 hours per day`
297
+ - `Prof. Kavitha should have Wednesday free`
298
+ """)
299
+
300
+ st.divider()
301
+
302
+ prompt = st.text_input("💬 Enter your instruction:",
303
+ placeholder="e.g. Prof. Anu is not available on Friday")
304
+
305
+ col1, col2 = st.columns([1, 3])
306
+ with col1:
307
+ apply = st.button("✅ Apply Change", type="primary",
308
+ disabled=not prompt.strip())
309
+ with col2:
310
+ preview = st.button("👁️ Preview Constraint Only",
311
+ disabled=not prompt.strip())
312
+
313
+ # Preview mode — just show the constraint without applying
314
+ if preview and prompt.strip():
315
+ from slm_inference import smart_parse
316
+ local = smart_parse(prompt, data['faculties'],
317
+ data.get('subjects',[]), data.get('sections',[]))
318
+ if local:
319
+ st.subheader("Constraint that would be applied (local parser):")
320
+ st.json(local)
321
+ else:
322
+ with st.spinner("Calling SLM API..."):
323
+ result = get_constraint(prompt)
324
+ if result.get('success'):
325
+ st.subheader("Constraint that would be applied (SLM API):")
326
+ for c in result['constraints']:
327
+ st.json(c)
328
+ else:
329
+ st.error(f"Failed to parse: {result.get('error')}")
330
+ st.code(result.get('raw', ''))
331
+
332
+ # Apply mode — actually update the timetable
333
+ if apply and prompt.strip():
334
+ from slm_inference import smart_parse
335
+
336
+ # ── Step 1: Try smart_parse FIRST for high-confidence direct operations ──
337
+ # These patterns are reliably detected locally without needing the SLM
338
+ priority_keywords = [
339
+ # Faculty operations
340
+ 'replace','substitute','take over','will take','on leave','cover',
341
+ 'permanently','change faculty','hand over','assign all',
342
+ # Cancel / holiday
343
+ 'cancel','no class','holiday','off day',
344
+ 'no toc','no nlp','no ml','no cn','no sepm','no nosql',
345
+ 'no rmipr','no dvlab','no cnlab','no nlplab','no mllab',
346
+ 'no iks','no evs','no genai','no devops','no hcai',
347
+ # Move / reschedule
348
+ 'move','shift','reschedule','transfer','relocate',
349
+ # Room
350
+ 'change room','to lab','to room','assign room',
351
+ # Extra class
352
+ 'extra class','makeup','compensatory','schedule extra',
353
+ 'add extra','add makeup','additional session','extra session',
354
+ 'extra ml','extra nlp','extra toc','extra cn','extra sepm',
355
+ 'schedule.*class','add.*class',
356
+ # Swap / freeze
357
+ 'swap','exchange','freeze','lock slot',
358
+ # NO_FREE_PERIOD — must be here so smart_parse runs first
359
+ 'should not be free','must not be free','cannot be free',
360
+ 'must have a class','should have a class','always occupied',
361
+ 'no free period','no free slot','must be filled',
362
+ 'first hour','first period','last period','last hour',
363
+ ]
364
+ use_smart_parse_first = any(kw in prompt.lower() for kw in priority_keywords)
365
+ constraints = []
366
+
367
+ if use_smart_parse_first:
368
+ local = smart_parse(prompt, data['faculties'],
369
+ data.get('subjects',[]), data.get('sections',[]))
370
+ if local:
371
+ constraints = [local]
372
+ st.info(f"🔄 Operation detected: `{local['type']}`")
373
+
374
+ # ── Step 2: Fall back to SLM API if smart_parse couldn't handle it ──
375
+ if not constraints:
376
+ with st.spinner("🤖 Converting instruction to constraint..."):
377
+ result = get_constraint(prompt)
378
+ if not result.get('success'):
379
+ st.error(f"❌ Could not parse instruction: {result.get('error')}")
380
+ st.code(result.get('raw', ''))
381
+ st.stop()
382
+ constraints = result.get('constraints', [])
383
+ if constraints:
384
+ st.info(f"Parsed constraint: `{constraints[0].get('type','?')}`")
385
+
386
+ if not constraints:
387
+ st.error("No constraints parsed."); st.stop()
388
+
389
+ st.info(f"Parsed constraint: `{constraints[0]['type'] if constraints else 'none'}`")
390
+
391
+ # Load current schedule and rebuild objects
392
+ sched = load_schedule()
393
+ current_solution = sched['schedule']
394
+ facs, subs, secs, rooms, allocs = rebuild_objects(data)
395
+ tasks = prepare_scheduling_tasks(allocs, facs, subs, secs)
396
+
397
+ # Rebuild task objects into solution (attach task_obj)
398
+ tasks_by_id = {t.task_id: t for t in tasks}
399
+ for tid, info in current_solution.items():
400
+ if tid in tasks_by_id:
401
+ info['task_obj'] = tasks_by_id[tid]
402
+
403
+ # Run partial optimizer for each constraint
404
+ all_changes = []
405
+ final_solution = current_solution
406
+
407
+ for constraint in constraints:
408
+ with st.spinner(f"Rescheduling tasks for: {constraint['type']}..."):
409
+ optimizer = PartialOptimizer(
410
+ tasks, facs, secs, rooms, final_solution)
411
+ status, new_solution, affected, summary = \
412
+ optimizer.apply_constraint_and_reoptimize(constraint)
413
+
414
+ if status in ('OPTIMAL', 'FEASIBLE', 'NO_CHANGE'):
415
+ final_solution = new_solution
416
+ all_changes.append(summary)
417
+ else:
418
+ st.warning(f"⚠️ {summary}")
419
+
420
+ # Save updated schedule
421
+ save_schedule(final_solution)
422
+ add_history_entry(
423
+ operation_type="LLM_UPDATE",
424
+ description=f"AI Update: {prompt}",
425
+ affected_sections=[],
426
+ changes=[],
427
+ status='SUCCESS',
428
+ constraints=constraints
429
+ )
430
+
431
+ # Show results
432
+ st.success("✅ Timetable updated!")
433
+ for change in all_changes:
434
+ st.markdown(change)
435
+
436
+ st.divider()
437
+
438
+ # ── Side-by-side: Original vs Updated ────────────────────────────
439
+ col_orig, col_new = st.columns(2)
440
+ with col_orig:
441
+ st.subheader("📋 Original Timetable")
442
+ render_timetable_html(current_solution, secs)
443
+ with col_new:
444
+ st.subheader("📅 Updated Timetable")
445
+ render_timetable_html(final_solution, secs)
446
+
447
+ # ── Show current timetable below ──────────────────────────────────────
448
+ st.divider()
449
+ with st.expander("📄 View Current Full Timetable"):
450
+ sched = load_schedule()
451
+ if sched:
452
+ _, _, secs, _, _ = rebuild_objects(data)
453
+ render_timetable_html(sched['schedule'], secs)
454
+
455
+ # ═════════════════════════════════════════════════════════════════════════════
456
+ # PAGE: ORIGINAL VS CURRENT
457
+ # ═════════════════════════════════════════════════════════════════════════════
458
+ elif page == "📊 Original vs Current":
459
+ st.header("📊 Original vs Current Timetable")
460
+
461
+ if not schedule_exists():
462
+ st.error("❌ No timetable generated yet.")
463
+ st.stop()
464
+
465
+ _, _, secs, _, _ = rebuild_objects(data)
466
+
467
+ has_orig = original_schedule_exists()
468
+ orig = load_original_schedule() if has_orig else None
469
+ current = load_schedule()
470
+
471
+ if orig:
472
+ orig_at = orig.get('generated_at', '')[:16].replace('T', ' ')
473
+ curr_at = current.get('generated_at', '')[:16].replace('T', ' ') if current else ''
474
+
475
+ # Summary badge
476
+ history = load_history()
477
+ n_changes = len(history)
478
+ col1, col2, col3 = st.columns(3)
479
+ col1.metric("Original Generated", orig_at if orig else "N/A")
480
+ col2.metric("Last Updated", curr_at)
481
+ col3.metric("Total Changes Applied", n_changes)
482
+
483
+ st.divider()
484
+
485
+ if orig:
486
+ tab1, tab2 = st.tabs(["🗂️ Original Timetable", "📅 Current Timetable"])
487
+ with tab1:
488
+ st.caption(f"Generated on {orig_at} — never modified")
489
+ render_timetable_html(orig['schedule'], secs)
490
+ with tab2:
491
+ st.caption(f"Last updated: {curr_at} — {n_changes} change(s) applied")
492
+ render_timetable_html(current['schedule'], secs)
493
+ else:
494
+ st.info("Original snapshot not available. "
495
+ "Regenerate the timetable to create one.")
496
+ st.subheader("Current Timetable")
497
+ render_timetable_html(current['schedule'], secs)
498
+
499
+ # ═════════════════════════════════════════════════════════════════════════════
500
+ # PAGE: CHANGE HISTORY
501
+ # ═════════════════════════════════════════════════════════════════════════════
502
+ elif page == "📋 Change History":
503
+ st.header("📋 Change History")
504
+
505
+ history = load_history()
506
+ if not history:
507
+ st.info("No changes have been made yet.")
508
+ else:
509
+ st.write(f"**{len(history)} change(s) recorded**")
510
+ for i, entry in enumerate(reversed(history)):
511
+ ts = entry['timestamp'][:16].replace('T', ' ')
512
+ desc = entry.get('description', entry.get('prompt', ''))
513
+ op_type = entry.get('operation_type', 'UPDATE')
514
+ with st.expander(f"#{len(history)-i} — {ts} — [{op_type}] {desc}"):
515
+ st.markdown(f"**Description:** {desc}")
516
+ st.markdown(f"**Operation:** {op_type}")
517
+ st.markdown(f"**Status:** {entry.get('status', 'UNKNOWN')}")
518
+ sections = entry.get('affected_sections', [])
519
+ if sections:
520
+ st.markdown(f"**Affected Sections:** {', '.join(sections)}")
521
+ changes = entry.get('changes', [])
522
+ if changes:
523
+ st.subheader(f"Detailed Changes ({len(changes)} cells)")
524
+ for c in changes[:20]:
525
+ st.json(c)
526
+ if entry.get('constraints'):
527
+ st.subheader("Constraint applied:")
528
+ st.json(entry['constraints'])
529
+
530
+ if st.button("🗑️ Clear History"):
531
+ clear_history()
532
+ st.rerun()
533
+
534
+ # ═════════════════════════════════════════════════════════════════════════════
535
+ # MANAGEMENT PAGES (unchanged from original)
536
+ # ═════════════════════════════════════════════════════════════════════════════
537
+ elif page == "👥 Manage Faculties":
538
+ st.header("Manage Faculties")
539
+ with st.form("add_faculty"):
540
+ col1, col2 = st.columns(2)
541
+ f_id = col1.text_input("ID (e.g., 'F001')")
542
+ f_name = col2.text_input("Name")
543
+ f_desig = col1.selectbox("Designation",
544
+ ["Professor","Assoc. Prof","Asst. Prof","Guest"])
545
+ f_max = col2.number_input("Max Hours/Week", min_value=1, value=18)
546
+ if st.form_submit_button("Add Faculty"):
547
+ if f_id and f_name:
548
+ data['faculties'].append({"id": f_id, "name": f_name,
549
+ "designation": f_desig, "max_hours": f_max})
550
+ save_data(data); st.success("Added!"); st.rerun()
551
+ else:
552
+ st.error("ID and Name are required.")
553
+ if data['faculties']:
554
+ st.dataframe(pd.DataFrame(data['faculties']))
555
+ if st.button("Clear All Faculties"):
556
+ data['faculties'] = []; save_data(data); st.rerun()
557
+
558
+ elif page == "📚 Manage Subjects":
559
+ st.header("Manage Subjects")
560
+ with st.form("add_subject"):
561
+ col1, col2 = st.columns(2)
562
+ s_code = col1.text_input("Code")
563
+ s_name = col2.text_input("Name")
564
+ s_type = col1.selectbox("Type", ["THEORY","LAB","SOFTSKILL","FORUM"])
565
+ s_cred = col2.number_input("Credits", min_value=0, value=3)
566
+ s_core = col1.checkbox("Is Core?", value=True)
567
+ s_heavy = col2.checkbox("Is Heavy?", value=False)
568
+ if st.form_submit_button("Add Subject"):
569
+ if s_code:
570
+ data['subjects'].append({"code": s_code, "name": s_name,
571
+ "type": s_type, "credits": s_cred,
572
+ "is_core": s_core, "is_heavy": s_heavy})
573
+ save_data(data); st.success("Added!"); st.rerun()
574
+ if data['subjects']:
575
+ st.dataframe(pd.DataFrame(data['subjects']))
576
+ if st.button("Clear All Subjects"):
577
+ data['subjects'] = []; save_data(data); st.rerun()
578
+
579
+ elif page == "🏛️ Manage Sections":
580
+ st.header("Manage Sections")
581
+ with st.form("add_section"):
582
+ col1, col2 = st.columns(2)
583
+ sec_id = col1.text_input("Section ID (e.g., '6A')")
584
+ sem = col2.number_input("Semester", min_value=1, value=6)
585
+ strength = col1.number_input("Student Strength", min_value=1, value=60)
586
+ if st.form_submit_button("Add Section"):
587
+ if sec_id:
588
+ data['sections'].append({"id": sec_id, "semester": sem,
589
+ "strength": strength})
590
+ save_data(data); st.success("Added!"); st.rerun()
591
+ if data['sections']:
592
+ st.dataframe(pd.DataFrame(data['sections']))
593
+ if st.button("Clear All Sections"):
594
+ data['sections'] = []; save_data(data); st.rerun()
595
+
596
+ elif page == "🚪 Manage Rooms":
597
+ st.header("Manage Rooms")
598
+ with st.form("add_room"):
599
+ col1, col2 = st.columns(2)
600
+ r_id = col1.text_input("Room ID")
601
+ cap = col2.number_input("Capacity", min_value=1, value=80)
602
+ is_lab = col1.checkbox("Is Lab?", value=False)
603
+ bld = col2.text_input("Building", value="Main")
604
+ if st.form_submit_button("Add Room"):
605
+ if r_id:
606
+ data['rooms'].append({"id": r_id, "capacity": cap,
607
+ "is_lab": is_lab, "building": bld})
608
+ save_data(data); st.success("Added!"); st.rerun()
609
+ if data['rooms']:
610
+ st.dataframe(pd.DataFrame(data['rooms']))
611
+ if st.button("Clear All Rooms"):
612
+ data['rooms'] = []; save_data(data); st.rerun()
613
+
614
+ elif page == "🔗 Manage Allocations":
615
+ st.header("Manage Allocations")
616
+ if not (data['faculties'] and data['subjects'] and data['sections']):
617
+ st.warning("Add Faculties, Subjects, and Sections first.")
618
+ else:
619
+ fac_opts = {f['name']: f['id'] for f in data['faculties']}
620
+ sub_opts = {s['name']: s['code'] for s in data['subjects']}
621
+ sec_opts = [s['id'] for s in data['sections']]
622
+ with st.form("add_alloc"):
623
+ col1, col2 = st.columns(2)
624
+ f = col1.selectbox("Faculty", list(fac_opts.keys()))
625
+ s = col2.selectbox("Subject", list(sub_opts.keys()))
626
+ sec = col1.selectbox("Section", sec_opts)
627
+ grp = col2.text_input("Elective Group ID (Optional)")
628
+ if st.form_submit_button("Add Allocation"):
629
+ data['allocations'].append({
630
+ "faculty_id": fac_opts[f],
631
+ "subject_code": sub_opts[s],
632
+ "section_id": sec,
633
+ "elective_group": grp if grp else None
634
+ })
635
+ save_data(data); st.success("Allocation Added!"); st.rerun()
636
+ if data['allocations']:
637
+ st.dataframe(pd.DataFrame(data['allocations']))
638
+ if st.button("Clear Allocations"):
639
+ data['allocations'] = []; save_data(data); st.rerun()
cancellations.json ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "id": "39048066-0726-4954-9e15-19d3810944fd",
4
+ "section_id": "7A",
5
+ "day": "Monday",
6
+ "period": 0,
7
+ "subject": "SOFT SKILLS",
8
+ "reason": "Slots not allocated yet",
9
+ "faculty_id": "Prof. Surbhi",
10
+ "status": "PENDING",
11
+ "created_at": "2026-06-30T09:59:46.230330"
12
+ },
13
+ {
14
+ "id": "e4e42303-3a01-4e50-8cb2-77a64f6e63dc",
15
+ "section_id": "7A",
16
+ "day": "Monday",
17
+ "period": 0,
18
+ "subject": "SOFT SKILLS",
19
+ "reason": "Slot not allocated",
20
+ "faculty_id": "Test Admin",
21
+ "status": "PENDING",
22
+ "created_at": "2026-06-30T10:01:39.988185"
23
+ }
24
+ ]
change_history.json ADDED
The diff for this file is too large to render. See raw diff
 
constants.py ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # constants.py
2
+
3
+ NUM_WORKING_DAYS = 5
4
+ DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
5
+
6
+ # 8 Teaching slots per day
7
+ NUM_TEACHING_SLOTS_PER_DAY = 8
8
+ TOTAL_TEACHING_SLOTS_PER_WEEK = NUM_TEACHING_SLOTS_PER_DAY * NUM_WORKING_DAYS
9
+
10
+ # INDICES (0-based) AFTER WHICH BREAKS OCCUR
11
+ # P1(0), P2(1) -> BREAK -> P3(2), P4(3) -> LUNCH -> P5(4)...
12
+ BREAK_AFTER_INDEX = 1
13
+ LUNCH_AFTER_INDEX = 3
14
+
15
+ # Valid Lab Start Indices (Computed to ensure labs don't cross breaks)
16
+ ALLOWED_LAB_START_INDICES = [0, 2, 4, 5, 6]
17
+
18
+ # Column Headers for Streamlit to match the reference image
19
+ TIMETABLE_HEADERS = [
20
+ "8:45-9:40", # P1
21
+ "9:40-10:35", # P2
22
+ "10:35-10:50", # BREAK
23
+ "10:50-11:45", # P3
24
+ "11:45-12:40", # P4
25
+ "12:40-1:40", # LUNCH
26
+ "1:40-2:35", # P5
27
+ "2:35-3:30", # P6
28
+ "3:30-4:25", # P7
29
+ "4:25-5:20" # P8
30
+ ]
constraint_engine.py ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # constraint_engine.py
2
+
3
+ from collections import defaultdict
4
+ from typing import List, Dict, Any, Tuple
5
+ from ortools.sat.python import cp_model
6
+ from models import Task, Faculty, Section, Room, SubjectType
7
+ import constants as const
8
+
9
+ TaskVariables = Tuple[cp_model.IntVar, cp_model.IntVar, cp_model.IntervalVar, cp_model.IntVar]
10
+
11
+ class ConstraintEngine:
12
+ def __init__(
13
+ self,
14
+ model: cp_model.CpModel,
15
+ tasks: List[Task],
16
+ faculties: List[Faculty],
17
+ sections: List[Section],
18
+ rooms: List[Room],
19
+ max_continuous_stretch: int = 3,
20
+ slm_constraints: List[Dict[str, Any]] = None,
21
+ scheduling_rules: List[Dict[str, Any]] = None,
22
+ locked_schedule: Dict[str, Any] = None,
23
+ locked_semesters: List[int] = None
24
+ ):
25
+ self.model = model
26
+ self.tasks = tasks
27
+ self.faculties = faculties
28
+ self.sections = sections
29
+ self.rooms = rooms
30
+ self.max_continuous_stretch = max_continuous_stretch
31
+ self.slm_constraints = slm_constraints or []
32
+ self.scheduling_rules = scheduling_rules or []
33
+ self.locked_schedule = locked_schedule or {}
34
+ self.locked_semesters = set(locked_semesters or [])
35
+ self.room_map = {room.room_id: i for i, room in enumerate(self.rooms)}
36
+ self.task_vars: Dict[str, TaskVariables] = {}
37
+
38
+ def apply_all_constraints(self):
39
+ self._create_task_variables()
40
+ self._apply_clash_constraints()
41
+ self._apply_stretch_constraints()
42
+ self._apply_grouping_constraints()
43
+ self._apply_daily_subject_limit()
44
+ self._apply_fixed_section_classroom_constraints()
45
+
46
+ def _create_task_variables(self):
47
+ for task in self.tasks:
48
+ is_locked = task.section.semester in self.locked_semesters
49
+
50
+ if is_locked and task.task_id in self.locked_schedule:
51
+ # Force the task to the exact slot and room from the existing schedule
52
+ locked_info = self.locked_schedule[task.task_id]
53
+ start_slot = locked_info["start_slot"]
54
+ room_id = locked_info["room_id"]
55
+ room_index = self.room_map.get(room_id, 0) # Fallback to 0 if missing
56
+
57
+ start_domain = cp_model.Domain.FromValues([start_slot])
58
+ start_var = self.model.NewIntVarFromDomain(start_domain, name=f"{task.task_id}_start")
59
+
60
+ room_domain = cp_model.Domain.FromValues([room_index])
61
+ room_var = self.model.NewIntVarFromDomain(room_domain, name=f"{task.task_id}_room")
62
+ else:
63
+ allowed_starts = self._get_allowed_start_slots(task)
64
+ start_domain = cp_model.Domain.FromValues(allowed_starts)
65
+ start_var = self.model.NewIntVarFromDomain(start_domain, name=f"{task.task_id}_start")
66
+
67
+ allowed_room_indices = self._get_allowed_rooms(task)
68
+ room_domain = cp_model.Domain.FromValues(allowed_room_indices)
69
+ room_var = self.model.NewIntVarFromDomain(room_domain, name=f"{task.task_id}_room")
70
+
71
+ end_var = self.model.NewIntVar(0, const.TOTAL_TEACHING_SLOTS_PER_WEEK, name=f"{task.task_id}_end")
72
+ interval_var = self.model.NewIntervalVar(start_var, task.duration, end_var, name=f"{task.task_id}_interval")
73
+
74
+ self.task_vars[task.task_id] = (start_var, end_var, interval_var, room_var)
75
+
76
+ def _apply_clash_constraints(self):
77
+ # 1. Faculty Clash Prevention (MODIFIED FOR COMBINED ELECTIVES)
78
+ intervals_by_faculty = defaultdict(list)
79
+ processed_groups_per_faculty = defaultdict(set)
80
+
81
+ faculty_ids_set = {f.id for f in self.faculties}
82
+ for task in self.tasks:
83
+ parts = task.faculty.id.split('_')
84
+ fids = parts if len(parts) > 1 and all(p in faculty_ids_set for p in parts) else [task.faculty.id]
85
+ gid = task.elective_group_id
86
+ interval = self.task_vars[task.task_id][2]
87
+
88
+ for fid in fids:
89
+ if gid:
90
+ # If this faculty is teaching a group, only add the interval ONCE for that group
91
+ if gid not in processed_groups_per_faculty[fid]:
92
+ intervals_by_faculty[fid].append(interval)
93
+ processed_groups_per_faculty[fid].add(gid)
94
+ else:
95
+ intervals_by_faculty[fid].append(interval)
96
+
97
+ for faculty_id in intervals_by_faculty:
98
+ if faculty_id == "DUMMY_STAFF":
99
+ continue
100
+ self.model.AddNoOverlap(intervals_by_faculty[faculty_id])
101
+
102
+ # 2. Section Clash Prevention (MODIFIED FOR ELECTIVES AND BATCHES)
103
+ intervals_by_section = defaultdict(list)
104
+ processed_groups_per_section = defaultdict(set)
105
+
106
+ # We also need to keep track of parent sections dynamically in case they are missing from self.sections
107
+ parent_sections_dynamic = set()
108
+
109
+ for task in self.tasks:
110
+ sid = task.section.section_id
111
+ gid = task.elective_group_id
112
+ interval = self.task_vars[task.task_id][2]
113
+
114
+ if gid:
115
+ if gid not in processed_groups_per_section[sid]:
116
+ intervals_by_section[sid].append(interval)
117
+ processed_groups_per_section[sid].add(gid)
118
+ else:
119
+ intervals_by_section[sid].append(interval)
120
+
121
+ # Add to parent_sections_dynamic if it's a parent section
122
+ if '-' not in sid:
123
+ parent_sections_dynamic.add(sid)
124
+ else:
125
+ parent_sections_dynamic.add(sid.split('-')[0])
126
+
127
+ for section_id in intervals_by_section:
128
+ self.model.AddNoOverlap(intervals_by_section[section_id])
129
+
130
+ # Enforce NoOverlap between Parent and ALL its Batches (including between batches)
131
+ for parent_sid in parent_sections_dynamic:
132
+ parent_intervals = list(intervals_by_section.get(parent_sid, []))
133
+
134
+ # Parent can't overlap with any batch task (grouped or not)
135
+ for batch_suffix in ['-B1', '-B2', '-B3']:
136
+ batch_id = f"{parent_sid}{batch_suffix}"
137
+ if batch_id in intervals_by_section:
138
+ self.model.AddNoOverlap(parent_intervals + intervals_by_section[batch_id])
139
+
140
+ # Non-grouped batch tasks across different batches can't overlap.
141
+ # (Grouped tasks are allowed to overlap — the grouping constraint
142
+ # forces them to the same start time intentionally.)
143
+ non_grouped_cross_batch = []
144
+ for task in self.tasks:
145
+ sid = task.section.section_id
146
+ if '-' in sid and sid.split('-')[0] == parent_sid and not task.elective_group_id:
147
+ non_grouped_cross_batch.append(self.task_vars[task.task_id][2])
148
+
149
+ if len(non_grouped_cross_batch) > 1:
150
+ self.model.AddNoOverlap(non_grouped_cross_batch)
151
+
152
+ # 3. Room Clash Prevention
153
+ for i, room in enumerate(self.rooms):
154
+ optional_intervals = []
155
+ for task in self.tasks:
156
+ if i in self._get_allowed_rooms(task):
157
+ interval, room_var = self.task_vars[task.task_id][2], self.task_vars[task.task_id][3]
158
+ is_in_room = self.model.NewBoolVar(f"{task.task_id}_in_room_{room.room_id}")
159
+ self.model.Add(room_var == i).OnlyEnforceIf(is_in_room)
160
+ self.model.Add(room_var != i).OnlyEnforceIf(is_in_room.Not())
161
+ opt_interval = self.model.NewOptionalIntervalVar(
162
+ interval.StartExpr(), interval.SizeExpr(), interval.EndExpr(), is_in_room, name=f"opt_{task.task_id}_{room.room_id}"
163
+ )
164
+ optional_intervals.append(opt_interval)
165
+ if optional_intervals:
166
+ self.model.AddNoOverlap(optional_intervals)
167
+
168
+ def _apply_stretch_constraints(self):
169
+ tasks_by_entity = defaultdict(list)
170
+ for task in self.tasks:
171
+ if task.faculty.id != "DUMMY_STAFF":
172
+ for fid in task.faculty.id.split('_'): # Handle composite co-teaching faculties
173
+ tasks_by_entity[f"fac_{fid}"].append(task)
174
+ tasks_by_entity[f"sec_{task.section.section_id}"].append(task)
175
+ for name, tasks in tasks_by_entity.items():
176
+ self._add_stretch_constraint_for_entity(tasks, name)
177
+
178
+ def _apply_grouping_constraints(self):
179
+ tasks_by_group = defaultdict(list)
180
+ for task in self.tasks:
181
+ if task.elective_group_id:
182
+ tasks_by_group[task.elective_group_id].append(task)
183
+ for group_tasks in tasks_by_group.values():
184
+ if len(group_tasks) > 1:
185
+ first_start = self.task_vars[group_tasks[0].task_id][0]
186
+ for i in range(1, len(group_tasks)):
187
+ self.model.Add(self.task_vars[group_tasks[i].task_id][0] == first_start)
188
+
189
+ def _apply_daily_subject_limit(self):
190
+ tasks_by_sec_sub = defaultdict(list)
191
+ for task in self.tasks:
192
+ if task.subject.subject_type == SubjectType.THEORY:
193
+ tasks_by_sec_sub[(task.section.section_id, task.subject.subject_code)].append(task)
194
+ for tasks in tasks_by_sec_sub.values():
195
+ for day in range(const.NUM_WORKING_DAYS):
196
+ literals = []
197
+ for task in tasks:
198
+ start_var = self.task_vars[task.task_id][0]
199
+ is_on_day = self.model.NewBoolVar(f"{task.task_id}_on_day_{day}")
200
+ day_idx = self.model.NewIntVar(0, const.NUM_WORKING_DAYS-1, f"day_{task.task_id}_{day}")
201
+ self.model.AddDivisionEquality(day_idx, start_var, const.NUM_TEACHING_SLOTS_PER_DAY)
202
+ self.model.Add(day_idx == day).OnlyEnforceIf(is_on_day)
203
+ self.model.Add(day_idx != day).OnlyEnforceIf(is_on_day.Not())
204
+ literals.append(is_on_day)
205
+ if literals: self.model.Add(sum(literals) <= 1)
206
+
207
+ def _get_allowed_start_slots(self, task: Task) -> List[int]:
208
+ full_range = set(range(const.TOTAL_TEACHING_SLOTS_PER_WEEK))
209
+ if task.duration > 1:
210
+ allowed_lab_slots = set()
211
+ for day in range(const.NUM_WORKING_DAYS):
212
+ day_offset = day * const.NUM_TEACHING_SLOTS_PER_DAY
213
+ for pos in const.ALLOWED_LAB_START_INDICES:
214
+ allowed_lab_slots.add(day_offset + pos)
215
+ full_range.intersection_update(allowed_lab_slots)
216
+
217
+ # ── Configurable scheduling rules (data-driven) ─────────────────
218
+ DAY_MAP = {'MON':0,'TUE':1,'WED':2,'THU':3,'FRI':4,'SAT':5}
219
+ for rule in self.scheduling_rules:
220
+ rtype = rule.get('rule_type', '').upper()
221
+
222
+ if rtype == 'FACULTY_UNAVAILABLE':
223
+ rule_fid = rule.get('faculty_id')
224
+ if rule_fid and rule_fid != task.faculty.id:
225
+ continue
226
+
227
+ days = [DAY_MAP[d.upper()] for d in (rule.get('days') or []) if d.upper() in DAY_MAP]
228
+ period_idx = rule.get('period_index')
229
+
230
+ for day in days:
231
+ off = day * const.NUM_TEACHING_SLOTS_PER_DAY
232
+ if period_idx is not None:
233
+ for o in range(task.duration):
234
+ full_range.discard(off + period_idx - o)
235
+ else:
236
+ for p in range(const.NUM_TEACHING_SLOTS_PER_DAY):
237
+ for o in range(task.duration):
238
+ full_range.discard(off + p - o)
239
+ continue
240
+
241
+ rule_subjects = [s.lower() for s in (rule.get('subject_codes') or [])]
242
+ rule_types = [t.upper() for t in (rule.get('subject_types') or [])]
243
+ # Check if this rule applies to the current task
244
+ matches_subject = task.subject.subject_code.lower() in rule_subjects if rule_subjects else False
245
+ matches_type = task.subject.subject_type.name in rule_types if rule_types else False
246
+ if not matches_subject and not matches_type:
247
+ continue
248
+
249
+ if rtype == 'FIXED_PERIOD':
250
+ # Force tasks to a specific period index
251
+ period_idx = rule.get('period_index', 0)
252
+ full_range = {s for s in full_range
253
+ if (s % const.NUM_TEACHING_SLOTS_PER_DAY) == period_idx}
254
+
255
+ elif rtype == 'BEFORE_TIME':
256
+ # Tasks must start at or before a max period index
257
+ max_p = rule.get('max_period_index', 4)
258
+ full_range = {s for s in full_range
259
+ if (s % const.NUM_TEACHING_SLOTS_PER_DAY) <= max_p}
260
+
261
+ elif rtype == 'FIXED_DAYS':
262
+ # Tasks must only be on specific days
263
+ allowed_days = {DAY_MAP[d.upper()] for d in (rule.get('days') or []) if d.upper() in DAY_MAP}
264
+ if allowed_days:
265
+ full_range = {s for s in full_range
266
+ if (s // const.NUM_TEACHING_SLOTS_PER_DAY) in allowed_days}
267
+
268
+ if self.slm_constraints:
269
+ DAY_INDEX = {'MON':0,'TUE':1,'WED':2,'THU':3,'FRI':4,'SAT':5}
270
+ MORNING_PERIODS = list(range(0, const.NUM_TEACHING_SLOTS_PER_DAY // 2))
271
+ AFTERNOON_PERIODS = list(range(const.NUM_TEACHING_SLOTS_PER_DAY // 2, const.NUM_TEACHING_SLOTS_PER_DAY))
272
+ for c in self.slm_constraints:
273
+ ctype = c.get('type', '').upper()
274
+ if ctype == 'FACULTY_UNAVAILABLE' and c.get('faculty_id') == task.faculty.id:
275
+ days = [DAY_INDEX[d] for d in (c.get('days') or []) if d in DAY_INDEX]
276
+ for day in days:
277
+ off = day * const.NUM_TEACHING_SLOTS_PER_DAY
278
+ for p in range(const.NUM_TEACHING_SLOTS_PER_DAY):
279
+ for o in range(task.duration):
280
+ full_range.discard(off + p - o)
281
+ elif ctype == 'SUBJECT_PREFERRED_TIME':
282
+ if c.get('subject_code') == task.subject.subject_code:
283
+ period = str(c.get('period','')).upper()
284
+ target = MORNING_PERIODS if 'MORNING' in period else AFTERNOON_PERIODS
285
+ if target:
286
+ full_range = {s for s in full_range if (s % const.NUM_TEACHING_SLOTS_PER_DAY) in target}
287
+ elif ctype == 'WORKING_DAYS':
288
+ allowed_days = {DAY_INDEX[d] for d in (c.get('days') or []) if d in DAY_INDEX}
289
+ if allowed_days:
290
+ full_range = {s for s in full_range if (s // const.NUM_TEACHING_SLOTS_PER_DAY) in allowed_days}
291
+ elif ctype == 'SECTION_FREE_SLOT' and c.get('section_id') == task.section.section_id:
292
+ slot_index = c.get('slot')
293
+ days = [DAY_INDEX[d] for d in (c.get('days') or []) if d in DAY_INDEX]
294
+ if not days:
295
+ days = list(range(const.NUM_WORKING_DAYS))
296
+ if slot_index is not None:
297
+ for day in days:
298
+ off = day * const.NUM_TEACHING_SLOTS_PER_DAY
299
+ for o in range(task.duration):
300
+ full_range.discard(off + slot_index - 1 - o)
301
+
302
+ return sorted(list(full_range))
303
+
304
+ def _get_allowed_rooms(self, task: Task) -> List[int]:
305
+ if task.subject.subject_code in ["LIB_HR", "STU_HR", "FAC_HR", "STDY_HR"]:
306
+ return [-1]
307
+
308
+ allowed = []
309
+ for i, room in enumerate(self.rooms):
310
+ type_match = (task.subject.subject_type == SubjectType.LAB) == room.is_lab
311
+ cap_match = room.capacity >= task.section.student_strength
312
+ if type_match and cap_match: allowed.append(i)
313
+ if not allowed:
314
+ req_type = 'Computer Lab' if task.subject.subject_type == SubjectType.LAB else 'Standard Classroom'
315
+ raise ValueError(f"Shortage of suitable class rooms! Could not find any {req_type} with a minimum capacity of {task.section.student_strength} students for the subject '{task.subject.name}' (Section {task.section.section_id}). Please add a larger room to the template.")
316
+ return allowed
317
+
318
+ def _add_stretch_constraint_for_entity(self, tasks: List[Task], entity_name: str):
319
+ if not tasks: return
320
+ for day in range(const.NUM_WORKING_DAYS):
321
+ for i in range(const.NUM_TEACHING_SLOTS_PER_DAY - self.max_continuous_stretch):
322
+ window_start = day * const.NUM_TEACHING_SLOTS_PER_DAY + i
323
+ window_slots = range(window_start, window_start + self.max_continuous_stretch + 1)
324
+ literals = []
325
+ for slot in window_slots:
326
+ is_active = self.model.NewBoolVar(f"{entity_name}_act_{slot}")
327
+ at_slot = []
328
+ for t in tasks:
329
+ start, dur = self.task_vars[t.task_id][0], t.duration
330
+ cov = self.model.NewBoolVar(f"{t.task_id}_cov_{slot}")
331
+ self.model.Add(start <= slot).OnlyEnforceIf(cov)
332
+ self.model.Add(start + dur > slot).OnlyEnforceIf(cov)
333
+ at_slot.append(cov)
334
+ self.model.AddBoolOr(at_slot).OnlyEnforceIf(is_active)
335
+ for lit in at_slot: self.model.AddImplication(is_active.Not(), lit.Not())
336
+ literals.append(is_active)
337
+ self.model.Add(sum(literals) <= self.max_continuous_stretch)
338
+
339
+ def _apply_fixed_section_classroom_constraints(self):
340
+ """
341
+ Enforce that all regular (non-lab, non-elective) theory classes of each parent section
342
+ are held in the same fixed 'Home Classroom'.
343
+ Rooms only change for Labs (computer/hardware labs) and Open Electives.
344
+ """
345
+ from collections import defaultdict
346
+ regular_tasks_by_parent = defaultdict(list)
347
+
348
+ for task in self.tasks:
349
+ # Skip locked tasks
350
+ if task.section.semester in self.locked_semesters and task.task_id in self.locked_schedule:
351
+ continue
352
+
353
+ # Only theory classes stay in the home classroom
354
+ if task.subject.subject_type == SubjectType.THEORY:
355
+ # Do not constrain split electives where students divide across multiple rooms
356
+ gid = task.elective_group_id or ""
357
+ if not gid or gid.startswith("CORE_"):
358
+ sid = task.section.section_id
359
+ # Skip sub-batches (e.g. 5A-B1)
360
+ if "-B" not in sid:
361
+ parent_sec = sid.split("-")[0]
362
+ regular_tasks_by_parent[parent_sec].append(task)
363
+
364
+ for parent_sec, tasks in regular_tasks_by_parent.items():
365
+ if len(tasks) == 0:
366
+ continue
367
+ first_room_var = self.task_vars[tasks[0].task_id][3]
368
+
369
+ # Check if this section has an explicit home_room_id specified
370
+ home_room_id = getattr(tasks[0].section, 'home_room_id', None)
371
+ if home_room_id and home_room_id in self.room_map:
372
+ target_room_index = self.room_map[home_room_id]
373
+ self.model.Add(first_room_var == target_room_index)
374
+
375
+ for other_task in tasks[1:]:
376
+ other_room_var = self.task_vars[other_task.task_id][3]
377
+ self.model.Add(other_room_var == first_room_var)
data.json ADDED
The diff for this file is too large to render. See raw diff
 
data_loader.py ADDED
@@ -0,0 +1,232 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # data_loader.py
2
+
3
+ """
4
+ This module is responsible for converting raw input data into a list of
5
+ atomic 'Task' objects that the solver can schedule.
6
+
7
+ Responsibilities:
8
+ - Ingest lists of faculties, subjects, sections, and their allocations.
9
+ - Convert subject credits into the correct number of weekly Task instances.
10
+ - Ensure THEORY subjects create 1-hour tasks per credit.
11
+ - Ensure LAB, SOFTSKILL, and FORUM subjects create a single 2-hour task.
12
+ - Assign a unique ID to each task.
13
+ - Populate each task with its corresponding faculty, subject, and section.
14
+ - Correctly handle elective groups for multi-hour subjects.
15
+
16
+ This module performs NO scheduling logic, constraint creation, or optimization.
17
+ It is purely a data transformation and preparation layer.
18
+ """
19
+
20
+ from dataclasses import dataclass
21
+ from typing import List, Optional
22
+
23
+ # Import the core data models
24
+ from models import Faculty, Subject, Section, Task, SubjectType
25
+ from constants import TOTAL_TEACHING_SLOTS_PER_WEEK
26
+
27
+ @dataclass(frozen=True)
28
+ class Allocation:
29
+ """
30
+ A simple dataclass to represent the raw input mapping of who teaches what
31
+ to whom. This is a cleaner alternative to using tuples or dicts.
32
+ """
33
+ faculty_id: str
34
+ subject_code: str
35
+ section_id: str
36
+ elective_group_id: Optional[str] = None
37
+
38
+
39
+ def prepare_scheduling_tasks(
40
+ allocations: List[Allocation],
41
+ faculties: List[Faculty],
42
+ subjects: List[Subject],
43
+ sections: List[Section]
44
+ ) -> List[Task]:
45
+ """
46
+ Processes raw allocation data and generates a flat list of atomic Task
47
+ objects ready for the solver.
48
+
49
+ Args:
50
+ allocations: A list of Allocation objects defining the teaching load.
51
+ faculties: A list of all available Faculty objects.
52
+ subjects: A list of all available Subject objects.
53
+ sections: A list of all available Section objects.
54
+
55
+ Returns:
56
+ A list of Task objects, where each task is an atomic unit to be scheduled.
57
+ """
58
+ # Create lookup dictionaries for efficient access
59
+ faculties_by_id = {f.id: f for f in faculties}
60
+ subjects_by_code = {s.subject_code: s for s in subjects}
61
+ sections_by_id = {s.section_id: s for s in sections}
62
+
63
+ all_tasks: List[Task] = []
64
+ section_duration_sums = {s.section_id: 0 for s in sections}
65
+
66
+ # Group allocations for co-teaching support
67
+ from collections import defaultdict
68
+ grouped_allocs = defaultdict(list)
69
+ for alloc in allocations:
70
+ key = (alloc.subject_code, alloc.section_id, alloc.elective_group_id)
71
+ grouped_allocs[key].append(alloc)
72
+
73
+ for key, alloc_group in grouped_allocs.items():
74
+ subject_code, section_id, alloc_elective_group_id = key
75
+
76
+ try:
77
+ subject = subjects_by_code[subject_code]
78
+ section = sections_by_id[section_id]
79
+ facs = [faculties_by_id[a.faculty_id] for a in alloc_group]
80
+ except KeyError as e:
81
+ print(f"Error: Invalid ID in allocation group {key}. Missing key: {e}")
82
+ continue
83
+
84
+ if len(facs) == 1:
85
+ faculty = facs[0]
86
+ else:
87
+ # Create composite faculty to avoid generating duplicate tasks
88
+ comp_id = "_".join(f.id for f in facs)
89
+ comp_name = " / ".join(f.name.replace("Prof. ", "").replace("Dr. ", "").replace("Mr. ", "").replace("Ms. ", "").strip() for f in facs)
90
+ faculty = Faculty(id=comp_id, name=comp_name, designation="Co-Teaching", max_hours_per_week=99)
91
+
92
+ # --- Task Generation Logic ---
93
+
94
+ if subject.subject_type == SubjectType.THEORY:
95
+ # For a theory subject, create one 1-hour task for each credit.
96
+ for i in range(subject.credits):
97
+ # *** CRITICAL FIX FOR ELECTIVES ***
98
+ # For a 3-credit elective, we need groups like 'group_0', 'group_1', 'group_2'
99
+ # to pair the correct hours across different sections.
100
+
101
+ group_id = None
102
+ if alloc_elective_group_id:
103
+ group_id = f"{alloc_elective_group_id}_{i}"
104
+ elif subject.is_core:
105
+ # Treat Core Theory as a "Joint Class" for all batches in the section (e.g. 6a-E1 + 6a-E2)
106
+ # We generate a synthetic group ID: CORE_CS101_6A_0
107
+ parent_sec = section.section_id.split('-')[0]
108
+ group_id = f"CORE_{subject.subject_code}_{parent_sec}_{i}"
109
+
110
+ task = Task(
111
+ task_id=f"{subject.subject_code}-{section.section_id}-{i}",
112
+ faculty=faculty,
113
+ subject=subject,
114
+ section=section,
115
+ duration=1,
116
+ elective_group_id=group_id
117
+ )
118
+ all_tasks.append(task)
119
+ section_duration_sums[section.section_id] += task.duration
120
+
121
+ elif subject.subject_type in [SubjectType.LAB, SubjectType.SOFTSKILL, SubjectType.FORUM]:
122
+ # For labs and other block sessions, create exactly ONE 2-hour task.
123
+ # The elective group applies to the entire 2-hour block as a single unit.
124
+ task = Task(
125
+ task_id=f"{subject.subject_code}-{section.section_id}-BLOCK",
126
+ faculty=faculty,
127
+ subject=subject,
128
+ section=section,
129
+ duration=2,
130
+ elective_group_id=alloc_elective_group_id
131
+ )
132
+ all_tasks.append(task)
133
+ section_duration_sums[section.section_id] += task.duration
134
+ # --- Gap Filler Logic ---
135
+ try:
136
+ if "DUMMY_STAFF" in faculties_by_id and "LIB_HR" in subjects_by_code:
137
+ dummy_fac = faculties_by_id["DUMMY_STAFF"]
138
+ dummy_subjects_pool = [
139
+ subjects_by_code["LIB_HR"],
140
+ subjects_by_code["STU_HR"],
141
+ subjects_by_code["FAC_HR"],
142
+ subjects_by_code["STDY_HR"]
143
+ ]
144
+
145
+ for section in sections:
146
+ if '-' in section.section_id or section.section_id == 'OE_AI':
147
+ continue
148
+ batch_durations = [
149
+ section_duration_sums.get(b.section_id, 0)
150
+ for b in sections if b.section_id.startswith(f"{section.section_id}-")
151
+ ]
152
+ max_batch_duration = max(batch_durations) if batch_durations else 0
153
+ current_duration = section_duration_sums.get(section.section_id, 0) + max_batch_duration
154
+
155
+ # Dynamically adjust filler buffer based on problem size (massive math = lower fillers)
156
+ num_allocations = len(allocations)
157
+ if num_allocations > 100:
158
+ buffer = 8 # Massive math: keep 8 hours free to reduce dummy tasks
159
+ elif num_allocations > 50:
160
+ buffer = 4 # Moderate math: keep 4 hours free
161
+ else:
162
+ buffer = 2 # Normal math: keep 2 hours free
163
+
164
+ gaps = max(0, TOTAL_TEACHING_SLOTS_PER_WEEK - current_duration - buffer)
165
+
166
+ if gaps > 0:
167
+ for i in range(gaps):
168
+ subj = dummy_subjects_pool[i % len(dummy_subjects_pool)]
169
+ task = Task(
170
+ task_id=f"FILLER-{subj.subject_code}-{section.section_id}-{i}",
171
+ faculty=dummy_fac,
172
+ subject=subj,
173
+ section=section,
174
+ duration=1,
175
+ elective_group_id=None
176
+ )
177
+ all_tasks.append(task)
178
+ # No need to update duration_sums since we are done
179
+ except KeyError as e:
180
+ print(f"Skipping gap filling, missing dummy setup: {e}")
181
+
182
+ return all_tasks
183
+
184
+
185
+ # --- Example Usage ---
186
+ if __name__ == '__main__':
187
+ # This block demonstrates how to use the prepare_scheduling_tasks function.
188
+ # It will only run when this file is executed directly.
189
+
190
+ # 1. Define sample data using the core models
191
+ faculty1 = Faculty(id="F001", name="Dr. Smith", designation="Professor", max_hours_per_week=10)
192
+ faculty2 = Faculty(id="F002", name="Dr. Jones", designation="Asst. Professor", max_hours_per_week=12)
193
+
194
+ subject_theory = Subject(subject_code="CS101", name="Intro to CS", credits=4, subject_type=SubjectType.THEORY)
195
+ subject_lab = Subject(subject_code="CS101L", name="CS Lab", credits=1, subject_type=SubjectType.LAB)
196
+ subject_elective = Subject(subject_code="CS555", name="Advanced AI", credits=3, subject_type=SubjectType.THEORY)
197
+
198
+
199
+ section_a = Section(section_id="5A", semester=5, student_strength=60)
200
+ section_b = Section(section_id="5B", semester=5, student_strength=62)
201
+
202
+ # 2. Define the teaching allocations
203
+ sample_allocations = [
204
+ Allocation(faculty_id="F001", subject_code="CS101", section_id="5A"),
205
+ Allocation(faculty_id="F002", subject_code="CS101L", section_id="5A"),
206
+ # Elective subject taught by the same faculty to both sections
207
+ Allocation(faculty_id="F001", subject_code="CS555", section_id="5A", elective_group_id="ELEC01"),
208
+ Allocation(faculty_id="F001", subject_code="CS555", section_id="5B", elective_group_id="ELEC01"),
209
+ ]
210
+
211
+ # 3. Call the data loader function
212
+ generated_tasks = prepare_scheduling_tasks(
213
+ allocations=sample_allocations,
214
+ faculties=[faculty1, faculty2],
215
+ subjects=[subject_theory, subject_lab, subject_elective],
216
+ sections=[section_a, section_b]
217
+ )
218
+
219
+ # 4. Print the results to verify
220
+ print(f"--- Generated {len(generated_tasks)} Tasks ---\n")
221
+ for task in generated_tasks:
222
+ print(f"Task ID: {task.task_id}")
223
+ print(f" Subject: {task.subject.name} ({task.subject.subject_type.name})")
224
+ print(f" Faculty: {task.faculty.name}")
225
+ print(f" Section: {task.section.section_id}")
226
+ print(f" Duration: {task.duration} hour(s)")
227
+ if task.elective_group_id:
228
+ print(f" Elective Group: {task.elective_group_id}")
229
+ print("-" * 20)
230
+
231
+ # Expected Output:
232
+ # Elective tasks for CS555 will now have group IDs like "ELEC01_0", "ELEC01_1", "ELEC01_2"
debug_counts.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import json
3
+
4
+ def check_counts():
5
+ try:
6
+ with open('master_dataset.json', 'r') as f:
7
+ content = f.read().strip()
8
+ if not content:
9
+ print("master_dataset.json is empty")
10
+ return
11
+
12
+ try:
13
+ # Try JSON Lines
14
+ lines = content.split('\n')
15
+ master_data = [json.loads(line) for line in lines if line.strip()]
16
+ print(f"master_dataset.json (JSON Lines): {len(master_data)} entries")
17
+ except json.JSONDecodeError:
18
+ # Try standard JSON
19
+ master_data = json.loads(content)
20
+ print(f"master_dataset.json (JSON List): {len(master_data)} entries")
21
+
22
+ # Check internal duplicates in master
23
+ unique_master = len(set(json.dumps(d, sort_keys=True) for d in master_data))
24
+ print(f"Unique entries in master_dataset.json: {unique_master}")
25
+
26
+ except Exception as e:
27
+ print(f"Error checking master: {e}")
28
+
29
+ try:
30
+ with open('data.json', 'r') as f:
31
+ data = json.load(f)
32
+ print(f"data.json: {len(data)} entries")
33
+ except Exception as e:
34
+ print(f"Error checking data.json: {e}")
35
+
36
+ if __name__ == "__main__":
37
+ check_counts()
debug_display.py ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import json
3
+ from collections import defaultdict
4
+ from models import Faculty, Subject, Section, Room, SubjectType
5
+ from data_loader import Allocation, prepare_scheduling_tasks
6
+ from solver import TimetableSolver
7
+ import constants as const
8
+
9
+ # --- Models Helpers ---
10
+ def get_subject_type_enum(type_str):
11
+ mapping = {
12
+ "THEORY": SubjectType.THEORY,
13
+ "LAB": SubjectType.LAB,
14
+ "SOFTSKILL": SubjectType.SOFTSKILL,
15
+ "FORUM": SubjectType.FORUM
16
+ }
17
+ return mapping.get(type_str.upper(), SubjectType.THEORY)
18
+
19
+ def convert_json_to_objects(data):
20
+ fac_objs = [Faculty(f['id'], f['name'], f['designation'], f['max_hours']) for f in data['faculties']]
21
+
22
+ sub_objs = [Subject(
23
+ s['code'], s['name'], s['credits'],
24
+ get_subject_type_enum(s['type']),
25
+ s.get('is_core', True), s.get('is_heavy', False)
26
+ ) for s in data['subjects']]
27
+
28
+ sec_objs = [Section(s['id'], s['semester'], s['strength']) for s in data['sections']]
29
+
30
+ room_objs = [Room(r['id'], r['capacity'], r['is_lab'], r['building']) for r in data['rooms']]
31
+
32
+ alloc_objs = [Allocation(
33
+ a['faculty_id'], a['subject_code'], a['section_id'], a.get('elective_group')
34
+ ) for a in data['allocations']]
35
+
36
+ return fac_objs, sub_objs, sec_objs, room_objs, alloc_objs
37
+
38
+ def main():
39
+ print("Loading data...")
40
+ with open("timetable_data.json", "r") as f:
41
+ data = json.load(f)
42
+
43
+ facs, subs, secs, rooms, allocs = convert_json_to_objects(data)
44
+ tasks = prepare_scheduling_tasks(allocs, facs, subs, secs)
45
+
46
+ print(f"Scheduling {len(tasks)} tasks...")
47
+
48
+ solver = TimetableSolver(tasks, facs, secs, rooms)
49
+ status, solution = solver.solve(time_limit_seconds=60)
50
+
51
+ if status not in ["OPTIMAL", "FEASIBLE"]:
52
+ print(f"Solver failed: {status}")
53
+ return
54
+
55
+ print("\n--- Display Logic Simulation ---")
56
+
57
+ # 1. Identify Parent Sections
58
+ parent_sections = sorted(list(set(s.section_id.split('-')[0].upper() for s in secs)))
59
+ print(f"Parent Sections Found: {parent_sections}")
60
+
61
+ # 2. Build Grid: merged_grid[parent_sec][day][period] = set of contents
62
+ merged_grid = defaultdict(lambda: defaultdict(lambda: defaultdict(set)))
63
+
64
+ for task_id, info in solution.items():
65
+ task_obj = info.get('task_obj')
66
+ sec_id = info['section_id']
67
+ parent_sec = sec_id.split('-')[0].upper()
68
+
69
+ day, period, dur = info['day_index'], info['period_index'], info['duration']
70
+
71
+ cell_content = info['subject_code'].upper()
72
+
73
+ # Fill grid slots
74
+ for i in range(dur):
75
+ merged_grid[parent_sec][day][period + i].add(cell_content)
76
+
77
+ # 3. Print Grid as Dict
78
+ import pprint
79
+ for p_sec, days in merged_grid.items():
80
+ print(f"\nSECTION: {p_sec}")
81
+ for day, periods in days.items():
82
+ print(f" Day {day}:")
83
+ # The instruction implies a change from 'in' to '==' for a check.
84
+ # The provided snippet introduces new lines with '==' but no 'in' to replace.
85
+ # Assuming 'header_text' would be defined in a more complete context,
86
+ # and applying the '==' as shown in the snippet.
87
+ # For this specific context, 'header_text' is undefined, so these lines are commented out
88
+ # or would require further context to be functional.
89
+ # is_break = (header_text == "10:35-10:50")
90
+ # is_lunch = (header_text == "12:40-1:40")
91
+ for period, contents in sorted(periods.items()):
92
+ print(f" Period {period}: {contents}")
93
+
94
+ if __name__ == "__main__":
95
+ main()
leave_requests.json ADDED
@@ -0,0 +1 @@
 
 
1
+ []
main.py ADDED
@@ -0,0 +1,254 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # main.py
2
+
3
+ """
4
+ This is the main entry point for the VTU Automated Timetable Generator.
5
+
6
+ It performs the following steps:
7
+ 1. Defines sample academic data (Faculties, Subjects, Sections, Rooms).
8
+ 2. Uses the data_loader to convert this data into atomic 'Task' objects.
9
+ 3. Initializes the TimetableSolver.
10
+ 4. Runs the solver to generate a valid timetable.
11
+ 5. Prints the resulting schedule in a human-readable grid format.
12
+ 6. (Optional) Demonstrates the Emergency Re-optimizer functionality.
13
+
14
+ This file is a standalone script and can be replaced by a UI or API layer.
15
+ """
16
+
17
+ import sys
18
+ from typing import List, Dict, Any
19
+
20
+ # Import project modules
21
+ from models import Faculty, Subject, Section, Room, SubjectType
22
+ from data_loader import Allocation, prepare_scheduling_tasks
23
+ from solver import TimetableSolver
24
+ from reoptimizer import EmergencyReoptimizer
25
+ import constants as const
26
+
27
+ def create_sample_data():
28
+ """
29
+ Creates mock data for a Computer Science department (3rd & 5th Semester).
30
+ """
31
+ print("Creating sample data...")
32
+
33
+ # --- 1. Faculties ---
34
+ # Define a mix of senior and junior faculties
35
+ faculties = [
36
+ Faculty("F01", "Dr. Alice", "Professor", max_hours_per_week=12),
37
+ Faculty("F02", "Prof. Bob", "Assoc. Prof", max_hours_per_week=16),
38
+ Faculty("F03", "Prof. Charlie", "Asst. Prof", max_hours_per_week=18),
39
+ Faculty("F04", "Prof. Dave", "Asst. Prof", max_hours_per_week=18),
40
+ Faculty("F05", "Prof. Eve", "Asst. Prof", max_hours_per_week=18),
41
+ Faculty("F06", "Guest Fac", "Guest", max_hours_per_week=8),
42
+ ]
43
+
44
+ # --- 2. Subjects ---
45
+ # Core Subjects, Labs, and Electives
46
+ subjects = [
47
+ # 5th Sem
48
+ Subject("CS51", "Mgmt & Entrepren", 3, SubjectType.THEORY),
49
+ Subject("CS52", "Computer Networks", 4, SubjectType.THEORY, is_core=True, is_heavy=True),
50
+ Subject("CS53", "Database Mgmt", 4, SubjectType.THEORY, is_core=True, is_heavy=True),
51
+ Subject("CS54", "Automata Theory", 3, SubjectType.THEORY, is_core=True),
52
+ Subject("CS55", "Python Elective", 3, SubjectType.THEORY), # Elective
53
+ Subject("CS56", "Java Elective", 3, SubjectType.THEORY), # Elective
54
+ Subject("CSL57", "Networks Lab", 1, SubjectType.LAB), # 2-hour block
55
+ Subject("CSL58", "DBMS Lab", 1, SubjectType.LAB), # 2-hour block
56
+
57
+ # 3rd Sem
58
+ Subject("CS31", "Maths III", 3, SubjectType.THEORY, is_core=True),
59
+ Subject("CS32", "Data Structures", 4, SubjectType.THEORY, is_core=True, is_heavy=True),
60
+ Subject("CS33", "Analog Digital", 3, SubjectType.THEORY),
61
+ Subject("CS34", "COA", 3, SubjectType.THEORY),
62
+ Subject("CSL37", "DS Lab", 1, SubjectType.LAB),
63
+ Subject("CSL38", "AD Lab", 1, SubjectType.LAB),
64
+ ]
65
+
66
+ # --- 3. Sections ---
67
+ sections = [
68
+ Section("5A", 5, 60),
69
+ Section("5B", 5, 60),
70
+ Section("3A", 3, 65),
71
+ ]
72
+
73
+ # --- 4. Rooms ---
74
+ rooms = [
75
+ # Classrooms
76
+ Room("R101", 70, is_lab=False, building="Main Block"),
77
+ Room("R102", 70, is_lab=False, building="Main Block"),
78
+ Room("R103", 70, is_lab=False, building="Main Block"),
79
+ # Labs
80
+ Room("LAB1", 30, is_lab=True, building="Lab Block"), # Small lab
81
+ Room("LAB2", 70, is_lab=True, building="Lab Block"), # Big lab
82
+ ]
83
+
84
+ # --- 5. Allocations (Who teaches what to whom) ---
85
+ allocations = [
86
+ # --- 5th Sem Section A ---
87
+ Allocation("F01", "CS51", "5A"),
88
+ Allocation("F02", "CS52", "5A"),
89
+ Allocation("F03", "CS53", "5A"),
90
+ Allocation("F04", "CS54", "5A"),
91
+ # Elective: Group 1 (Split class)
92
+ Allocation("F05", "CS55", "5A", elective_group_id="ELEC_5_GRP1"),
93
+ # Labs
94
+ Allocation("F02", "CSL57", "5A"),
95
+ Allocation("F03", "CSL58", "5A"),
96
+
97
+ # --- 5th Sem Section B ---
98
+ Allocation("F01", "CS51", "5B"),
99
+ Allocation("F02", "CS52", "5B"),
100
+ Allocation("F03", "CS53", "5B"),
101
+ Allocation("F04", "CS54", "5B"),
102
+ # Elective: Same Group ID to align slot (if cross-section) or different if purely parallel
103
+ # Here we assume 5A and 5B might have electives at same time
104
+ Allocation("F06", "CS56", "5B", elective_group_id="ELEC_5_GRP1"),
105
+ # Labs
106
+ Allocation("F02", "CSL57", "5B"),
107
+ Allocation("F03", "CSL58", "5B"),
108
+
109
+ # --- 3rd Sem Section A ---
110
+ Allocation("F04", "CS31", "3A"),
111
+ Allocation("F05", "CS32", "3A"),
112
+ Allocation("F06", "CS33", "3A"),
113
+ Allocation("F01", "CS34", "3A"),
114
+ Allocation("F05", "CSL37", "3A"),
115
+ Allocation("F06", "CSL38", "3A"),
116
+ ]
117
+
118
+ return faculties, subjects, sections, rooms, allocations
119
+
120
+
121
+ def print_timetable_grid(solution: Dict[str, Any], sections: List[Section]):
122
+ """
123
+ Prints the generated timetable with explicit Break and Lunch columns.
124
+ """
125
+ if not solution:
126
+ print("No solution to display.")
127
+ return
128
+
129
+ # 1. Organize data into a nested dictionary
130
+ # Structure: grid[section_id][day_index][period_index] = "Subject (Faculty)"
131
+ grid = {sec.section_id: {d: {} for d in range(const.NUM_WORKING_DAYS)} for sec in sections}
132
+
133
+ for task_id, info in solution.items():
134
+ sec_id = info['section_id']
135
+ day = info['day_index']
136
+ start_period = info['period_index']
137
+ duration = info['duration']
138
+
139
+ # Format the label
140
+ # e.g., "NLP (Anu) [R1]"
141
+ label = f"{info['subject_code']} ({info['faculty_name']}) [{info['room_id']}]"
142
+
143
+ for i in range(duration):
144
+ current_period = start_period + i
145
+ if current_period < const.NUM_TEACHING_SLOTS_PER_DAY:
146
+ grid[sec_id][day][current_period] = label
147
+
148
+ # 2. Print the Grid
149
+ for sec in sections:
150
+ print(f"\n{'='*100}")
151
+ print(f"TIMETABLE FOR SECTION: {sec.section_id}")
152
+ print(f"{'='*100}")
153
+
154
+ # --- Build Header Row ---
155
+ header = f"{'DAY':<10} |"
156
+ separator = f"{'-'*10}-+"
157
+
158
+ for i in range(const.NUM_TEACHING_SLOTS_PER_DAY):
159
+ # Print Period Number
160
+ header += f" P{i+1:<13} |"
161
+ separator += f"{'-'*15}-+"
162
+
163
+ # Inject Break Header
164
+ if i == const.BREAK_AFTER_INDEX:
165
+ header += " BREAK (15m) |"
166
+ separator += f"{'-'*13}-+"
167
+ # Inject Lunch Header
168
+ elif i == const.LUNCH_AFTER_INDEX:
169
+ header += " LUNCH (1h) |"
170
+ separator += f"{'-'*13}-+"
171
+
172
+ print(header)
173
+ print(separator)
174
+
175
+ # --- Build Data Rows ---
176
+ for d_idx, day_name in enumerate(const.DAYS):
177
+ row = f"{day_name:<10} |"
178
+
179
+ for p_idx in range(const.NUM_TEACHING_SLOTS_PER_DAY):
180
+ # Get the class info, default to empty
181
+ cell_data = grid[sec.section_id][d_idx].get(p_idx, "")
182
+
183
+ # Truncate to fit column
184
+ row += f" {cell_data[:13]:<13} |"
185
+
186
+ # Inject Break Column
187
+ if p_idx == const.BREAK_AFTER_INDEX:
188
+ row += f" {'***':<11} |"
189
+ # Inject Lunch Column
190
+ elif p_idx == const.LUNCH_AFTER_INDEX:
191
+ row += f" {'---':<11} |"
192
+
193
+ print(row)
194
+ print(separator)
195
+
196
+
197
+ def main():
198
+ # 1. Load Data
199
+ faculties, subjects, sections, rooms, allocations = create_sample_data()
200
+
201
+ # 2. Prepare Tasks
202
+ print(f"Generating tasks from {len(allocations)} allocations...")
203
+ tasks = prepare_scheduling_tasks(allocations, faculties, subjects, sections)
204
+ print(f"Total atomic tasks to schedule: {len(tasks)}")
205
+
206
+ # 3. Initialize Solver
207
+ solver = TimetableSolver(tasks, faculties, sections, rooms)
208
+
209
+ # 4. Solve
210
+ print("\nRunning Solver...")
211
+ status, solution = solver.solve(
212
+ time_limit_seconds=10,
213
+ enable_soft_constraints=True,
214
+ soft_constraint_weights={
215
+ "subject_repetition": 10,
216
+ "morning_core": 5,
217
+ "late_heavy": 10,
218
+ "faculty_gaps": 2,
219
+ "campus_movement": 5,
220
+ "pack_morning": 500,
221
+ "student_gaps": 500
222
+ }
223
+ )
224
+
225
+ if status in ["OPTIMAL", "FEASIBLE"]:
226
+ # 5. Display Results
227
+ print_timetable_grid(solution, sections)
228
+
229
+ # 6. Emergency Re-optimization Demo
230
+ print("\n" + "!"*80)
231
+ print("SIMULATING EMERGENCY: Faculty 'Prof. Bob' (F02) takes leave on Tuesday.")
232
+ print("!"*80)
233
+
234
+ reoptimizer = EmergencyReoptimizer(tasks, faculties, sections, rooms)
235
+
236
+ # Tuesday is index 1
237
+ reopt_status, new_solution = reoptimizer.reoptimize_for_faculty_leave(
238
+ current_schedule=solution,
239
+ faculty_id="F02",
240
+ leave_day_index=1,
241
+ time_limit_seconds=10
242
+ )
243
+
244
+ if reopt_status in ["OPTIMAL", "FEASIBLE"]:
245
+ print("\nRe-optimized Timetable (Changes minimized):")
246
+ print_timetable_grid(new_solution, sections)
247
+ else:
248
+ print("Failed to re-optimize.")
249
+
250
+ else:
251
+ print(f"Solver failed to find a solution. Status: {status}")
252
+
253
+ if __name__ == "__main__":
254
+ main()
master_dataset.json ADDED
The diff for this file is too large to render. See raw diff
 
merge_datasets.py ADDED
@@ -0,0 +1,75 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import json
3
+
4
+ def load_data(filename):
5
+ data = []
6
+ try:
7
+ with open(filename, 'r') as f:
8
+ content = f.read().strip()
9
+ if not content:
10
+ print(f"Warning: {filename} is empty.")
11
+ return []
12
+
13
+ # Try parsing as standard JSON list
14
+ try:
15
+ data = json.loads(content)
16
+ if isinstance(data, list):
17
+ print(f"Successfully loaded {filename} as JSON list.")
18
+ return data
19
+ else:
20
+ print(f"Warning: {filename} is valid JSON but not a list (got {type(data)}). Wrapping in list.")
21
+ return [data]
22
+ except json.JSONDecodeError:
23
+ # Try parsing as JSON Lines (one JSON object per line)
24
+ try:
25
+ lines = content.split('\n')
26
+ data = [json.loads(line) for line in lines if line.strip()]
27
+ print(f"Successfully loaded {filename} as JSON Lines ({len(data)} entries).")
28
+ return data
29
+ except json.JSONDecodeError as e:
30
+ print(f"Error: Failed to parse {filename} as JSON or JSON Lines. Error: {e}")
31
+ return []
32
+ except FileNotFoundError:
33
+ print(f"Error: {filename} not found.")
34
+ return []
35
+
36
+ def merge_datasets():
37
+ master_data = load_data('master_dataset.json')
38
+ new_data = load_data('data.json')
39
+
40
+ print(f"Entries from master_dataset.json: {len(master_data)}")
41
+ print(f"Entries from data.json: {len(new_data)}")
42
+
43
+ if not master_data and not new_data:
44
+ print("No data loaded. Exiting.")
45
+ return
46
+
47
+ # Combine lists
48
+ combined_data = master_data + new_data
49
+
50
+ # Remove duplicates
51
+ unique_data = []
52
+ seen = set()
53
+
54
+ for entry in combined_data:
55
+ # Sort keys to ensure consistent string representation
56
+ # Use simple string checks to avoid complex object issues
57
+ try:
58
+ entry_str = json.dumps(entry, sort_keys=True)
59
+ if entry_str not in seen:
60
+ seen.add(entry_str)
61
+ unique_data.append(entry)
62
+ except TypeError:
63
+ print(f"Warning: Skipping non-serializable entry: {entry}")
64
+
65
+ print(f"Total entries after merge (removing duplicates): {len(unique_data)}")
66
+
67
+ # Write to new file as JSON List (standard format for most tools)
68
+ output_filename = 'merged_dataset.json'
69
+ with open(output_filename, 'w') as f:
70
+ json.dump(unique_data, f, indent=2)
71
+
72
+ print(f"Merged data saved to {output_filename}")
73
+
74
+ if __name__ == "__main__":
75
+ merge_datasets()
merged_dataset.json ADDED
The diff for this file is too large to render. See raw diff
 
models.py ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # models.py
2
+
3
+ """
4
+ This file defines all core data models for the VTU Automated Timetable Generator.
5
+
6
+ These models are solver-agnostic and serve as the standard data structures
7
+ used throughout the application, from data loading to constraint modeling.
8
+
9
+ No OR-Tools or other solver-specific imports are allowed in this file.
10
+ """
11
+
12
+ from enum import Enum, auto
13
+ from dataclasses import dataclass, field
14
+ from typing import List, Optional, Set
15
+
16
+ # --- Enumerations ---
17
+
18
+ class SubjectType(Enum):
19
+ """
20
+ Enumeration for the type of a subject.
21
+ This helps in applying specific constraints, like duration.
22
+ """
23
+ THEORY = auto()
24
+ LAB = auto()
25
+ SOFTSKILL = auto()
26
+ FORUM = auto()
27
+
28
+ # --- Core Data Models ---
29
+
30
+ @dataclass(frozen=True)
31
+ class Faculty:
32
+ """
33
+ Represents a faculty member.
34
+ 'frozen=True' makes instances of this class immutable, which is a good
35
+ practice for data models to prevent accidental modification.
36
+ """
37
+ id: str
38
+ name: str
39
+ designation: str
40
+ max_hours_per_week: int
41
+ # Availability is a set of compressed slot indices (0-39) where the faculty
42
+ # IS available. If None, the faculty is assumed to be available always.
43
+ availability_slots: Optional[Set[int]] = None
44
+
45
+ @dataclass(frozen=True)
46
+ class Subject:
47
+ """
48
+ Represents a subject or a course.
49
+ """
50
+ subject_code: str
51
+ name: str
52
+ credits: int # 1 credit = 1 hour/week. Determines the number of classes.
53
+ subject_type: SubjectType
54
+ is_core: bool = True # Flag for core subjects
55
+ is_heavy: bool = False # Flag for computationally/conceptually heavy subjects
56
+
57
+ @dataclass(frozen=True)
58
+ class Section:
59
+ """
60
+ Represents a class section (e.g., '5th Sem A').
61
+ """
62
+ section_id: str
63
+ semester: int
64
+ student_strength: int
65
+ home_room_id: Optional[str] = None
66
+
67
+ @dataclass(frozen=True)
68
+ class Room:
69
+ """
70
+ Represents a physical room, either a classroom or a lab.
71
+ """
72
+ room_id: str
73
+ capacity: int
74
+ is_lab: bool = False
75
+ building: str = "Main" # Used for campus movement optimization
76
+
77
+ @dataclass(frozen=True)
78
+ class Task:
79
+ """
80
+ Represents an atomic, schedulable unit.
81
+ This is the fundamental element the CP-SAT solver will schedule.
82
+ It connects a faculty, a subject, and a section for a specific duration.
83
+ """
84
+ # A unique identifier for the task, e.g., f"{subject_code}-{section_id}-{instance_num}"
85
+ task_id: str
86
+ faculty: Faculty
87
+ subject: Subject
88
+ section: Section
89
+ # Duration in terms of number of continuous teaching slots.
90
+ # Labs, Soft Skills, and Forums are 2-hour blocks. Theory is 1 hour.
91
+ duration: int
92
+ # Optional ID to group tasks that must be scheduled at the same time.
93
+ # e.g., All tasks for a specific elective across different sections.
94
+ elective_group_id: Optional[str] = None
95
+
96
+ # --- Leave & Substitution Models ---
97
+
98
+ class LeaveStatus(str, Enum):
99
+ PENDING = "PENDING"
100
+ APPROVED = "APPROVED"
101
+ REJECTED = "REJECTED"
102
+
103
+ class SubstitutionStatus(str, Enum):
104
+ PENDING = "PENDING"
105
+ ACCEPTED = "ACCEPTED"
106
+ DECLINED = "DECLINED"
107
+ TIMEOUT = "TIMEOUT"
108
+ WITHDRAWN = "WITHDRAWN"
109
+
110
+ @dataclass
111
+ class AffectedSlot:
112
+ subject_code: str
113
+ section_id: str
114
+ day: str
115
+ period: int
116
+ room_id: str
117
+
118
+ @dataclass
119
+ class LeaveRequest:
120
+ leave_id: str
121
+ faculty_id: str
122
+ days: List[str] # e.g. ["Monday", "Tuesday"]
123
+ reason: str
124
+ status: LeaveStatus
125
+
126
+ @dataclass
127
+ class ProposedSwap:
128
+ subject_code: str
129
+ day: str
130
+ original_period: int
131
+ new_period: int
132
+
133
+ @dataclass
134
+ class SubstitutionRequest:
135
+ request_id: str
136
+ leave_id: str
137
+ affected_slot: AffectedSlot
138
+ original_faculty_id: str
139
+ candidate_faculty_id: str
140
+ priority_level: int
141
+ status: SubstitutionStatus
142
+ sent_at: str # ISO format datetime
143
+ expires_at: str # ISO format datetime
144
+ proposed_swap: Optional[ProposedSwap] = None
145
+ responded_at: Optional[str] = None
objective_engine.py ADDED
@@ -0,0 +1,407 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # objective_engine.py
2
+
3
+ from collections import defaultdict
4
+ from typing import List, Dict, Any
5
+ from ortools.sat.python import cp_model
6
+ from models import Task, Faculty, Section, SubjectType
7
+ from constraint_engine import ConstraintEngine
8
+ import constants as const
9
+
10
+ class ObjectiveEngine:
11
+ def __init__(
12
+ self,
13
+ model: cp_model.CpModel,
14
+ ce: ConstraintEngine,
15
+ tasks: List[Task],
16
+ faculties: List[Faculty],
17
+ sections: List[Section],
18
+ weights: Dict[str, int] = None
19
+ ):
20
+ self.model = model
21
+ self.ce = ce
22
+ self.tasks = tasks
23
+ self.faculties = faculties
24
+ self.sections = sections
25
+
26
+ self.weights = weights or {
27
+ "subject_repetition": 100,
28
+ "morning_core": 50,
29
+ "late_heavy": 50,
30
+ "faculty_gaps": 5000,
31
+ "student_gaps": 10000,
32
+ "isolated_afternoon": 1000,
33
+ "campus_movement": 30,
34
+ "faculty_load_balance": 10,
35
+ "no_first_hour_free": 5000,
36
+ "pack_morning": 200,
37
+ "avoid_late_afternoon": 200
38
+ }
39
+
40
+ self.penalties: List[cp_model.IntVar] = []
41
+
42
+ # Precompute common variables for extreme efficiency
43
+ self.t_day = {}
44
+ self.t_dstart = {}
45
+ self.t_dend = {}
46
+
47
+ for task in self.tasks:
48
+ start_var = self.ce.task_vars[task.task_id][0]
49
+
50
+ day = self.model.NewIntVar(0, const.NUM_WORKING_DAYS - 1, f"t_{task.task_id}_day")
51
+ self.model.AddDivisionEquality(day, start_var, const.NUM_TEACHING_SLOTS_PER_DAY)
52
+ self.t_day[task.task_id] = day
53
+
54
+ d_start = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY - 1, f"t_{task.task_id}_dstart")
55
+ self.model.AddModuloEquality(d_start, start_var, const.NUM_TEACHING_SLOTS_PER_DAY)
56
+ self.t_dstart[task.task_id] = d_start
57
+
58
+ # Note: End slot could reach NUM_TEACHING_SLOTS_PER_DAY (which means it ends at the very end of the day)
59
+ d_end = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, f"t_{task.task_id}_dend")
60
+ self.model.Add(d_end == d_start + task.duration)
61
+ self.t_dend[task.task_id] = d_end
62
+
63
+ def build_objective(self):
64
+ """
65
+ Applies all configured soft constraints and sets the minimization objective.
66
+ """
67
+ print("Building Objective Function...")
68
+
69
+ self._minimize_subject_repetition()
70
+ self._prioritize_morning_core_subjects()
71
+ self._avoid_late_heavy_subjects()
72
+
73
+ self._minimize_faculty_gaps()
74
+ self._minimize_student_gaps()
75
+ self._penalize_isolated_afternoon_classes()
76
+ self._minimize_campus_movement()
77
+ self._penalize_first_hour_free()
78
+
79
+ self._penalize_empty_morning_slots()
80
+ self._penalize_late_afternoon_slots()
81
+
82
+ # Summation of all penalties
83
+ if self.penalties:
84
+ total_cost = sum(self.penalties)
85
+ self.model.Minimize(total_cost)
86
+ else:
87
+ self.model.Minimize(0)
88
+
89
+ def _minimize_subject_repetition(self):
90
+ weight = self.weights.get("subject_repetition", 0)
91
+ if weight == 0: return
92
+
93
+ tasks_by_sec_sub = {}
94
+ for task in self.tasks:
95
+ if task.subject.subject_type == SubjectType.THEORY:
96
+ key = (task.section.section_id, task.subject.subject_code)
97
+ if key not in tasks_by_sec_sub:
98
+ tasks_by_sec_sub[key] = []
99
+ tasks_by_sec_sub[key].append(task)
100
+
101
+ for (sec_id, sub_code), subject_tasks in tasks_by_sec_sub.items():
102
+ if len(subject_tasks) < 2: continue
103
+
104
+ for i in range(len(subject_tasks)):
105
+ for j in range(i + 1, len(subject_tasks)):
106
+ t1 = subject_tasks[i]
107
+ t2 = subject_tasks[j]
108
+
109
+ day_1 = self.t_day[t1.task_id]
110
+ day_2 = self.t_day[t2.task_id]
111
+
112
+ same_day = self.model.NewBoolVar(f"same_day_{t1.task_id}_{t2.task_id}")
113
+ self.model.Add(day_1 == day_2).OnlyEnforceIf(same_day)
114
+ self.model.Add(day_1 != day_2).OnlyEnforceIf(same_day.Not())
115
+
116
+ self.penalties.append(same_day * weight)
117
+
118
+ def _prioritize_morning_core_subjects(self):
119
+ weight = self.weights.get("morning_core", 0)
120
+ if weight == 0: return
121
+
122
+ afternoon_start_index = 4
123
+
124
+ for task in self.tasks:
125
+ if task.subject.is_core and task.subject.subject_type == SubjectType.THEORY:
126
+ daily_slot = self.t_dstart[task.task_id]
127
+
128
+ is_afternoon = self.model.NewBoolVar(f"is_afternoon_{task.task_id}")
129
+ self.model.Add(daily_slot >= afternoon_start_index).OnlyEnforceIf(is_afternoon)
130
+ self.model.Add(daily_slot < afternoon_start_index).OnlyEnforceIf(is_afternoon.Not())
131
+
132
+ self.penalties.append(is_afternoon * weight)
133
+
134
+ def _avoid_late_heavy_subjects(self):
135
+ weight = self.weights.get("late_heavy", 0)
136
+ if weight == 0: return
137
+
138
+ last_slot_index = const.NUM_TEACHING_SLOTS_PER_DAY - 1
139
+
140
+ for task in self.tasks:
141
+ if task.subject.is_heavy:
142
+ daily_slot = self.t_dstart[task.task_id]
143
+
144
+ is_last_slot = self.model.NewBoolVar(f"is_last_slot_{task.task_id}")
145
+ self.model.Add(daily_slot == last_slot_index).OnlyEnforceIf(is_last_slot)
146
+ self.model.Add(daily_slot != last_slot_index).OnlyEnforceIf(is_last_slot.Not())
147
+
148
+ self.penalties.append(is_last_slot * weight)
149
+
150
+ def _minimize_faculty_gaps(self):
151
+ weight = self.weights.get("faculty_gaps", 30)
152
+ if weight == 0: return
153
+
154
+ tasks_by_faculty = {f.id: [] for f in self.faculties}
155
+ faculty_ids_set = {f.id for f in self.faculties}
156
+ for task in self.tasks:
157
+ parts = task.faculty.id.split('_')
158
+ fids = parts if len(parts) > 1 and all(p in faculty_ids_set for p in parts) else [task.faculty.id]
159
+ for fid in fids:
160
+ if fid in tasks_by_faculty:
161
+ tasks_by_faculty[fid].append(task)
162
+
163
+ for faculty_id, f_tasks in tasks_by_faculty.items():
164
+ if not f_tasks: continue
165
+
166
+ for day in range(const.NUM_WORKING_DAYS):
167
+ min_start_on_day = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, f"fac_{faculty_id}_min_s_d{day}")
168
+ max_end_on_day = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, f"fac_{faculty_id}_max_e_d{day}")
169
+
170
+ start_vars = [const.NUM_TEACHING_SLOTS_PER_DAY]
171
+ end_vars = [0]
172
+ total_duration = 0
173
+
174
+ for task in f_tasks:
175
+ is_on_day = self.model.NewBoolVar(f"fac_{faculty_id}_on_d{day}_t{task.task_id}")
176
+ t_day = self.t_day[task.task_id]
177
+ self.model.Add(t_day == day).OnlyEnforceIf(is_on_day)
178
+ self.model.Add(t_day != day).OnlyEnforceIf(is_on_day.Not())
179
+
180
+ start_on_day = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, "")
181
+ self.model.Add(start_on_day == self.t_dstart[task.task_id]).OnlyEnforceIf(is_on_day)
182
+ self.model.Add(start_on_day == const.NUM_TEACHING_SLOTS_PER_DAY).OnlyEnforceIf(is_on_day.Not())
183
+ start_vars.append(start_on_day)
184
+
185
+ end_on_day = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, "")
186
+ self.model.Add(end_on_day == self.t_dend[task.task_id]).OnlyEnforceIf(is_on_day)
187
+ self.model.Add(end_on_day == 0).OnlyEnforceIf(is_on_day.Not())
188
+ end_vars.append(end_on_day)
189
+
190
+ total_duration += task.duration * is_on_day
191
+
192
+ self.model.AddMinEquality(min_start_on_day, start_vars)
193
+ self.model.AddMaxEquality(max_end_on_day, end_vars)
194
+
195
+ gap = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, f"fac_{faculty_id}_gap_d{day}")
196
+ self.model.AddMaxEquality(gap, [0, max_end_on_day - min_start_on_day - total_duration])
197
+ self.penalties.append(gap * weight)
198
+
199
+ def _minimize_student_gaps(self):
200
+ weight = self.weights.get("student_gaps", 100)
201
+ if weight == 0: return
202
+
203
+ tasks_by_section = defaultdict(list)
204
+ for task in self.tasks:
205
+ tasks_by_section[task.section.section_id].append(task)
206
+
207
+ for sec_id, s_tasks in tasks_by_section.items():
208
+ if not s_tasks: continue
209
+
210
+ for day in range(const.NUM_WORKING_DAYS):
211
+ min_start_on_day = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, f"sec_{sec_id}_min_s_d{day}")
212
+ max_end_on_day = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, f"sec_{sec_id}_max_e_d{day}")
213
+
214
+ start_vars = [const.NUM_TEACHING_SLOTS_PER_DAY]
215
+ end_vars = [0]
216
+ total_duration = 0
217
+
218
+ for task in s_tasks:
219
+ is_on_day = self.model.NewBoolVar(f"sec_{sec_id}_on_d{day}_t{task.task_id}")
220
+ t_day = self.t_day[task.task_id]
221
+ self.model.Add(t_day == day).OnlyEnforceIf(is_on_day)
222
+ self.model.Add(t_day != day).OnlyEnforceIf(is_on_day.Not())
223
+
224
+ start_on_day = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, "")
225
+ self.model.Add(start_on_day == self.t_dstart[task.task_id]).OnlyEnforceIf(is_on_day)
226
+ self.model.Add(start_on_day == const.NUM_TEACHING_SLOTS_PER_DAY).OnlyEnforceIf(is_on_day.Not())
227
+ start_vars.append(start_on_day)
228
+
229
+ end_on_day = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, "")
230
+ self.model.Add(end_on_day == self.t_dend[task.task_id]).OnlyEnforceIf(is_on_day)
231
+ self.model.Add(end_on_day == 0).OnlyEnforceIf(is_on_day.Not())
232
+ end_vars.append(end_on_day)
233
+
234
+ total_duration += task.duration * is_on_day
235
+
236
+ self.model.AddMinEquality(min_start_on_day, start_vars)
237
+ self.model.AddMaxEquality(max_end_on_day, end_vars)
238
+
239
+ late_start_weight = self.weights.get("no_first_hour_free", 5000)
240
+
241
+ if weight > 0:
242
+ gap = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, f"sec_{sec_id}_gap_d{day}")
243
+ self.model.AddMaxEquality(gap, [0, max_end_on_day - min_start_on_day - total_duration])
244
+ self.penalties.append(gap * weight)
245
+
246
+ if late_start_weight > 0:
247
+ is_free_day = self.model.NewBoolVar(f"sec_{sec_id}_free_d{day}")
248
+ self.model.Add(total_duration == 0).OnlyEnforceIf(is_free_day)
249
+ self.model.Add(total_duration > 0).OnlyEnforceIf(is_free_day.Not())
250
+
251
+ late_start_penalty = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, f"sec_{sec_id}_late_d{day}")
252
+ self.model.Add(late_start_penalty == 0).OnlyEnforceIf(is_free_day)
253
+ self.model.Add(late_start_penalty == min_start_on_day).OnlyEnforceIf(is_free_day.Not())
254
+
255
+ self.penalties.append(late_start_penalty * late_start_weight)
256
+
257
+ def _penalize_isolated_afternoon_classes(self):
258
+ weight = self.weights.get("isolated_afternoon", 10)
259
+ if weight == 0: return
260
+
261
+ afternoon_start_index = 4
262
+
263
+ tasks_by_section = defaultdict(list)
264
+ for task in self.tasks:
265
+ tasks_by_section[task.section.section_id].append(task)
266
+
267
+ for sec_id, s_tasks in tasks_by_section.items():
268
+ for day in range(const.NUM_WORKING_DAYS):
269
+ afternoon_duration_sum = []
270
+
271
+ for task in s_tasks:
272
+ daily_slot = self.t_dstart[task.task_id]
273
+ t_day = self.t_day[task.task_id]
274
+
275
+ is_on_day = self.model.NewBoolVar(f"is_on_day_{task.task_id}_{day}_aft")
276
+ self.model.Add(t_day == day).OnlyEnforceIf(is_on_day)
277
+ self.model.Add(t_day != day).OnlyEnforceIf(is_on_day.Not())
278
+
279
+ is_afternoon = self.model.NewBoolVar(f"is_afternoon_{task.task_id}_{day}_aft")
280
+ self.model.Add(daily_slot >= afternoon_start_index).OnlyEnforceIf(is_afternoon)
281
+ self.model.Add(daily_slot < afternoon_start_index).OnlyEnforceIf(is_afternoon.Not())
282
+
283
+ is_on_day_and_afternoon = self.model.NewBoolVar(f"is_on_day_and_afternoon_{task.task_id}_{day}")
284
+ self.model.AddBoolAnd([is_on_day, is_afternoon]).OnlyEnforceIf(is_on_day_and_afternoon)
285
+
286
+ dur_term = self.model.NewIntVar(0, task.duration, f"aft_dur_{task.task_id}_{day}")
287
+ self.model.Add(dur_term == task.duration).OnlyEnforceIf(is_on_day_and_afternoon)
288
+ self.model.Add(dur_term == 0).OnlyEnforceIf(is_on_day_and_afternoon.Not())
289
+
290
+ afternoon_duration_sum.append(dur_term)
291
+
292
+ total_aft_duration = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY, f"total_aft_dur_sec{sec_id}_d{day}")
293
+ self.model.Add(total_aft_duration == sum(afternoon_duration_sum))
294
+
295
+ is_isolated = self.model.NewBoolVar(f"is_isolated_aft_sec{sec_id}_d{day}")
296
+
297
+ is_gt_0 = self.model.NewBoolVar(f"aft_gt_0_sec{sec_id}_d{day}")
298
+ self.model.Add(total_aft_duration > 0).OnlyEnforceIf(is_gt_0)
299
+ self.model.Add(total_aft_duration == 0).OnlyEnforceIf(is_gt_0.Not())
300
+
301
+ is_le_2 = self.model.NewBoolVar(f"aft_le_2_sec{sec_id}_d{day}")
302
+ self.model.Add(total_aft_duration <= 2).OnlyEnforceIf(is_le_2)
303
+ self.model.Add(total_aft_duration > 2).OnlyEnforceIf(is_le_2.Not())
304
+
305
+ self.model.AddBoolAnd([is_gt_0, is_le_2]).OnlyEnforceIf(is_isolated)
306
+
307
+ self.penalties.append(is_isolated * weight)
308
+
309
+ def _minimize_campus_movement(self):
310
+ weight = self.weights.get("campus_movement", 3)
311
+ if weight == 0: return
312
+
313
+ tasks_by_section = defaultdict(list)
314
+ for task in self.tasks:
315
+ tasks_by_section[task.section.section_id].append(task)
316
+
317
+ for sec_id, s_tasks in tasks_by_section.items():
318
+ if len(s_tasks) < 2: continue
319
+
320
+ for i in range(len(s_tasks)):
321
+ for j in range(i + 1, len(s_tasks)):
322
+ t1 = s_tasks[i]
323
+ t2 = s_tasks[j]
324
+
325
+ start1 = self.ce.task_vars[t1.task_id][0]
326
+ end1 = self.ce.task_vars[t1.task_id][1]
327
+ room1 = self.ce.task_vars[t1.task_id][3]
328
+
329
+ start2 = self.ce.task_vars[t2.task_id][0]
330
+ end2 = self.ce.task_vars[t2.task_id][1]
331
+ room2 = self.ce.task_vars[t2.task_id][3]
332
+
333
+ is_consecutive_1_2 = self.model.NewBoolVar(f"cons_{t1.task_id}_{t2.task_id}")
334
+ self.model.Add(end1 == start2).OnlyEnforceIf(is_consecutive_1_2)
335
+ self.model.Add(end1 != start2).OnlyEnforceIf(is_consecutive_1_2.Not())
336
+
337
+ is_consecutive_2_1 = self.model.NewBoolVar(f"cons_{t2.task_id}_{t1.task_id}")
338
+ self.model.Add(end2 == start1).OnlyEnforceIf(is_consecutive_2_1)
339
+ self.model.Add(end2 != start1).OnlyEnforceIf(is_consecutive_2_1.Not())
340
+
341
+ are_consecutive = self.model.NewBoolVar(f"are_cons_{t1.task_id}_{t2.task_id}")
342
+ self.model.AddBoolOr([is_consecutive_1_2, is_consecutive_2_1]).OnlyEnforceIf(are_consecutive)
343
+ self.model.AddBoolAnd([is_consecutive_1_2.Not(), is_consecutive_2_1.Not()]).OnlyEnforceIf(are_consecutive.Not())
344
+
345
+ same_room = self.model.NewBoolVar(f"same_room_{t1.task_id}_{t2.task_id}")
346
+ self.model.Add(room1 == room2).OnlyEnforceIf(same_room)
347
+ self.model.Add(room1 != room2).OnlyEnforceIf(same_room.Not())
348
+
349
+ move_penalty = self.model.NewBoolVar(f"move_{t1.task_id}_{t2.task_id}")
350
+ self.model.AddBoolAnd([are_consecutive, same_room.Not()]).OnlyEnforceIf(move_penalty)
351
+
352
+ self.penalties.append(move_penalty * weight)
353
+
354
+ def _penalize_first_hour_free(self):
355
+ pass
356
+
357
+ def _penalize_empty_morning_slots(self):
358
+ weight = self.weights.get("pack_morning", 50)
359
+ if weight == 0: return
360
+
361
+ tasks_by_section = defaultdict(list)
362
+ for task in self.tasks:
363
+ tasks_by_section[task.section.section_id].append(task)
364
+
365
+ for sec_id, s_tasks in tasks_by_section.items():
366
+ if not s_tasks: continue
367
+
368
+ morning_overlaps = []
369
+ for task in s_tasks:
370
+ d_start = self.t_dstart[task.task_id]
371
+ d_end = self.t_dend[task.task_id]
372
+
373
+ capped_start = self.model.NewIntVar(0, 4, "")
374
+ self.model.AddMinEquality(capped_start, [d_start, 4])
375
+
376
+ capped_end = self.model.NewIntVar(0, 4, "")
377
+ self.model.AddMinEquality(capped_end, [d_end, 4])
378
+
379
+ overlap = self.model.NewIntVar(0, 4, "")
380
+ self.model.Add(overlap == capped_end - capped_start)
381
+ morning_overlaps.append(overlap)
382
+
383
+ # Total morning slots are 20 (5 days * 4 slots)
384
+ total_slots = const.NUM_WORKING_DAYS * 4
385
+ empty_slots = self.model.NewIntVar(0, total_slots, f"sec_{sec_id}_empty_morning")
386
+ self.model.Add(empty_slots == total_slots - sum(morning_overlaps))
387
+
388
+ self.penalties.append(empty_slots * weight)
389
+
390
+ def _penalize_late_afternoon_slots(self):
391
+ weight = self.weights.get("avoid_late_afternoon", 40)
392
+ if weight == 0: return
393
+
394
+ for task in self.tasks:
395
+ d_start = self.t_dstart[task.task_id]
396
+ d_end = self.t_dend[task.task_id]
397
+
398
+ capped_start = self.model.NewIntVar(6, const.NUM_TEACHING_SLOTS_PER_DAY, "")
399
+ self.model.AddMaxEquality(capped_start, [d_start, 6])
400
+
401
+ capped_end = self.model.NewIntVar(6, const.NUM_TEACHING_SLOTS_PER_DAY, "")
402
+ self.model.AddMaxEquality(capped_end, [d_end, 6])
403
+
404
+ overlap = self.model.NewIntVar(0, const.NUM_TEACHING_SLOTS_PER_DAY - 6, "")
405
+ self.model.Add(overlap == capped_end - capped_start)
406
+
407
+ self.penalties.append(overlap * weight)
original_schedule.json ADDED
@@ -0,0 +1,2597 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "generated_at": "2026-07-08T03:56:08.146939",
3
+ "schedule": {
4
+ "DL&RL-7A-0": {
5
+ "start_slot": 24,
6
+ "day_index": 3,
7
+ "day_name": "Thursday",
8
+ "period_index": 0,
9
+ "room_id": "AI09 1F 25",
10
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
11
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
12
+ "subject_code": "DL&RL",
13
+ "section_id": "7A",
14
+ "duration": 1
15
+ },
16
+ "DL&RL-7A-1": {
17
+ "start_slot": 17,
18
+ "day_index": 2,
19
+ "day_name": "Wednesday",
20
+ "period_index": 1,
21
+ "room_id": "AI09 1F 25",
22
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
23
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
24
+ "subject_code": "DL&RL",
25
+ "section_id": "7A",
26
+ "duration": 1
27
+ },
28
+ "DL&RL-7A-2": {
29
+ "start_slot": 35,
30
+ "day_index": 4,
31
+ "day_name": "Friday",
32
+ "period_index": 3,
33
+ "room_id": "AI09 2F 14",
34
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
35
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
36
+ "subject_code": "DL&RL",
37
+ "section_id": "7A",
38
+ "duration": 1
39
+ },
40
+ "DL&RL-7B-0": {
41
+ "start_slot": 11,
42
+ "day_index": 1,
43
+ "day_name": "Tuesday",
44
+ "period_index": 3,
45
+ "room_id": "AI09 1F 25",
46
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
47
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
48
+ "subject_code": "DL&RL",
49
+ "section_id": "7B",
50
+ "duration": 1
51
+ },
52
+ "DL&RL-7B-1": {
53
+ "start_slot": 3,
54
+ "day_index": 0,
55
+ "day_name": "Monday",
56
+ "period_index": 3,
57
+ "room_id": "AI09 3F 01",
58
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
59
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
60
+ "subject_code": "DL&RL",
61
+ "section_id": "7B",
62
+ "duration": 1
63
+ },
64
+ "DL&RL-7B-2": {
65
+ "start_slot": 26,
66
+ "day_index": 3,
67
+ "day_name": "Thursday",
68
+ "period_index": 2,
69
+ "room_id": "AI09 1F26",
70
+ "room_name": "AI09 1F26 (ECE BLOCK)",
71
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
72
+ "subject_code": "DL&RL",
73
+ "section_id": "7B",
74
+ "duration": 1
75
+ },
76
+ "DL&RL Lab-7A-B1-BLOCK": {
77
+ "start_slot": 6,
78
+ "day_index": 0,
79
+ "day_name": "Monday",
80
+ "period_index": 6,
81
+ "room_id": "AI09 3F 03",
82
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
83
+ "faculty_name": "Dr. Kavitha Nair R",
84
+ "subject_code": "DL&RL Lab",
85
+ "section_id": "7A-B1",
86
+ "duration": 2
87
+ },
88
+ "DL&RL Lab-7A-B2-BLOCK": {
89
+ "start_slot": 8,
90
+ "day_index": 1,
91
+ "day_name": "Tuesday",
92
+ "period_index": 0,
93
+ "room_id": "AI09 2F 13",
94
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
95
+ "faculty_name": "Dr. Kavitha Nair R",
96
+ "subject_code": "DL&RL Lab",
97
+ "section_id": "7A-B2",
98
+ "duration": 2
99
+ },
100
+ "DL&RL Lab-7B-B1-BLOCK": {
101
+ "start_slot": 22,
102
+ "day_index": 2,
103
+ "day_name": "Wednesday",
104
+ "period_index": 6,
105
+ "room_id": "AI09 2F 14",
106
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
107
+ "faculty_name": "Mr. Praveen Arokia Raj",
108
+ "subject_code": "DL&RL Lab",
109
+ "section_id": "7B-B1",
110
+ "duration": 2
111
+ },
112
+ "DL&RL Lab-7B-B2-BLOCK": {
113
+ "start_slot": 24,
114
+ "day_index": 3,
115
+ "day_name": "Thursday",
116
+ "period_index": 0,
117
+ "room_id": "AI09 3F 08",
118
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
119
+ "faculty_name": "Mr. Praveen Arokia Raj",
120
+ "subject_code": "DL&RL Lab",
121
+ "section_id": "7B-B2",
122
+ "duration": 2
123
+ },
124
+ "ML II-7A-0": {
125
+ "start_slot": 11,
126
+ "day_index": 1,
127
+ "day_name": "Tuesday",
128
+ "period_index": 3,
129
+ "room_id": "AI09 3F 03",
130
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
131
+ "faculty_name": "Mr. Syed Musadiq Illahi",
132
+ "subject_code": "ML II",
133
+ "section_id": "7A",
134
+ "duration": 1
135
+ },
136
+ "ML II-7A-1": {
137
+ "start_slot": 1,
138
+ "day_index": 0,
139
+ "day_name": "Monday",
140
+ "period_index": 1,
141
+ "room_id": "AI09 1F26",
142
+ "room_name": "AI09 1F26 (ECE BLOCK)",
143
+ "faculty_name": "Mr. Syed Musadiq Illahi",
144
+ "subject_code": "ML II",
145
+ "section_id": "7A",
146
+ "duration": 1
147
+ },
148
+ "ML II-7A-2": {
149
+ "start_slot": 27,
150
+ "day_index": 3,
151
+ "day_name": "Thursday",
152
+ "period_index": 3,
153
+ "room_id": "AI09 1F 25",
154
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
155
+ "faculty_name": "Mr. Syed Musadiq Illahi",
156
+ "subject_code": "ML II",
157
+ "section_id": "7A",
158
+ "duration": 1
159
+ },
160
+ "ML II-7B-0": {
161
+ "start_slot": 19,
162
+ "day_index": 2,
163
+ "day_name": "Wednesday",
164
+ "period_index": 3,
165
+ "room_id": "AI09 2F 13",
166
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
167
+ "faculty_name": "Mr. Abhijith S",
168
+ "subject_code": "ML II",
169
+ "section_id": "7B",
170
+ "duration": 1
171
+ },
172
+ "ML II-7B-1": {
173
+ "start_slot": 1,
174
+ "day_index": 0,
175
+ "day_name": "Monday",
176
+ "period_index": 1,
177
+ "room_id": "AI09 3F 08",
178
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
179
+ "faculty_name": "Mr. Abhijith S",
180
+ "subject_code": "ML II",
181
+ "section_id": "7B",
182
+ "duration": 1
183
+ },
184
+ "ML II-7B-2": {
185
+ "start_slot": 35,
186
+ "day_index": 4,
187
+ "day_name": "Friday",
188
+ "period_index": 3,
189
+ "room_id": "AI09 1F26",
190
+ "room_name": "AI09 1F26 (ECE BLOCK)",
191
+ "faculty_name": "Mr. Abhijith S",
192
+ "subject_code": "ML II",
193
+ "section_id": "7B",
194
+ "duration": 1
195
+ },
196
+ "ML II Lab-7A-B1-BLOCK": {
197
+ "start_slot": 8,
198
+ "day_index": 1,
199
+ "day_name": "Tuesday",
200
+ "period_index": 0,
201
+ "room_id": "AI09 3F 08",
202
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
203
+ "faculty_name": "Mr. Syed Musadiq Illahi",
204
+ "subject_code": "ML II Lab",
205
+ "section_id": "7A-B1",
206
+ "duration": 2
207
+ },
208
+ "ML II Lab-7A-B2-BLOCK": {
209
+ "start_slot": 6,
210
+ "day_index": 0,
211
+ "day_name": "Monday",
212
+ "period_index": 6,
213
+ "room_id": "AI09 3F 08",
214
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
215
+ "faculty_name": "Mr. Syed Musadiq Illahi",
216
+ "subject_code": "ML II Lab",
217
+ "section_id": "7A-B2",
218
+ "duration": 2
219
+ },
220
+ "ML II Lab-7B-B1-BLOCK": {
221
+ "start_slot": 24,
222
+ "day_index": 3,
223
+ "day_name": "Thursday",
224
+ "period_index": 0,
225
+ "room_id": "AI09 2F 14",
226
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
227
+ "faculty_name": "Mr. Abhijith S",
228
+ "subject_code": "ML II Lab",
229
+ "section_id": "7B-B1",
230
+ "duration": 2
231
+ },
232
+ "ML II Lab-7B-B2-BLOCK": {
233
+ "start_slot": 22,
234
+ "day_index": 2,
235
+ "day_name": "Wednesday",
236
+ "period_index": 6,
237
+ "room_id": "AI09 1F 25",
238
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
239
+ "faculty_name": "Mr. Abhijith S",
240
+ "subject_code": "ML II Lab",
241
+ "section_id": "7B-B2",
242
+ "duration": 2
243
+ },
244
+ "DS&P-7A-0": {
245
+ "start_slot": 3,
246
+ "day_index": 0,
247
+ "day_name": "Monday",
248
+ "period_index": 3,
249
+ "room_id": "AI09 2F 14",
250
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
251
+ "faculty_name": "Mr. Jovin Deglus",
252
+ "subject_code": "DS&P",
253
+ "section_id": "7A",
254
+ "duration": 1
255
+ },
256
+ "DS&P-7A-1": {
257
+ "start_slot": 32,
258
+ "day_index": 4,
259
+ "day_name": "Friday",
260
+ "period_index": 0,
261
+ "room_id": "AI09 2F 14",
262
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
263
+ "faculty_name": "Mr. Jovin Deglus",
264
+ "subject_code": "DS&P",
265
+ "section_id": "7A",
266
+ "duration": 1
267
+ },
268
+ "DS&P-7A-2": {
269
+ "start_slot": 16,
270
+ "day_index": 2,
271
+ "day_name": "Wednesday",
272
+ "period_index": 0,
273
+ "room_id": "AI09 3F 08",
274
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
275
+ "faculty_name": "Mr. Jovin Deglus",
276
+ "subject_code": "DS&P",
277
+ "section_id": "7A",
278
+ "duration": 1
279
+ },
280
+ "DS&P-7A-3": {
281
+ "start_slot": 26,
282
+ "day_index": 3,
283
+ "day_name": "Thursday",
284
+ "period_index": 2,
285
+ "room_id": "AI09 3F 03",
286
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
287
+ "faculty_name": "Mr. Jovin Deglus",
288
+ "subject_code": "DS&P",
289
+ "section_id": "7A",
290
+ "duration": 1
291
+ },
292
+ "DS&P-7B-0": {
293
+ "start_slot": 27,
294
+ "day_index": 3,
295
+ "day_name": "Thursday",
296
+ "period_index": 3,
297
+ "room_id": "AI09 2F 12",
298
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
299
+ "faculty_name": "Mr. Jovin Deglus",
300
+ "subject_code": "DS&P",
301
+ "section_id": "7B",
302
+ "duration": 1
303
+ },
304
+ "DS&P-7B-1": {
305
+ "start_slot": 9,
306
+ "day_index": 1,
307
+ "day_name": "Tuesday",
308
+ "period_index": 1,
309
+ "room_id": "AI09 3F 01",
310
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
311
+ "faculty_name": "Mr. Jovin Deglus",
312
+ "subject_code": "DS&P",
313
+ "section_id": "7B",
314
+ "duration": 1
315
+ },
316
+ "DS&P-7B-2": {
317
+ "start_slot": 17,
318
+ "day_index": 2,
319
+ "day_name": "Wednesday",
320
+ "period_index": 1,
321
+ "room_id": "AI09 2F 08",
322
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
323
+ "faculty_name": "Mr. Jovin Deglus",
324
+ "subject_code": "DS&P",
325
+ "section_id": "7B",
326
+ "duration": 1
327
+ },
328
+ "DS&P-7B-3": {
329
+ "start_slot": 34,
330
+ "day_index": 4,
331
+ "day_name": "Friday",
332
+ "period_index": 2,
333
+ "room_id": "AI09 3F 08",
334
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
335
+ "faculty_name": "Mr. Jovin Deglus",
336
+ "subject_code": "DS&P",
337
+ "section_id": "7B",
338
+ "duration": 1
339
+ },
340
+ "BA-7A-0": {
341
+ "start_slot": 5,
342
+ "day_index": 0,
343
+ "day_name": "Monday",
344
+ "period_index": 5,
345
+ "room_id": "AI09 3F 01",
346
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
347
+ "faculty_name": "MBA Faculty",
348
+ "subject_code": "BA",
349
+ "section_id": "7A",
350
+ "duration": 1
351
+ },
352
+ "BA-7A-1": {
353
+ "start_slot": 31,
354
+ "day_index": 3,
355
+ "day_name": "Thursday",
356
+ "period_index": 7,
357
+ "room_id": "AI09 3F 03",
358
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
359
+ "faculty_name": "MBA Faculty",
360
+ "subject_code": "BA",
361
+ "section_id": "7A",
362
+ "duration": 1
363
+ },
364
+ "BA-7A-2": {
365
+ "start_slot": 33,
366
+ "day_index": 4,
367
+ "day_name": "Friday",
368
+ "period_index": 1,
369
+ "room_id": "AI09 3F 01",
370
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
371
+ "faculty_name": "MBA Faculty",
372
+ "subject_code": "BA",
373
+ "section_id": "7A",
374
+ "duration": 1
375
+ },
376
+ "BA-7B-0": {
377
+ "start_slot": 5,
378
+ "day_index": 0,
379
+ "day_name": "Monday",
380
+ "period_index": 5,
381
+ "room_id": "AI09 2F 08",
382
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
383
+ "faculty_name": "MBA Faculty",
384
+ "subject_code": "BA",
385
+ "section_id": "7B",
386
+ "duration": 1
387
+ },
388
+ "BA-7B-1": {
389
+ "start_slot": 31,
390
+ "day_index": 3,
391
+ "day_name": "Thursday",
392
+ "period_index": 7,
393
+ "room_id": "AI09 2F 14",
394
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
395
+ "faculty_name": "MBA Faculty",
396
+ "subject_code": "BA",
397
+ "section_id": "7B",
398
+ "duration": 1
399
+ },
400
+ "BA-7B-2": {
401
+ "start_slot": 33,
402
+ "day_index": 4,
403
+ "day_name": "Friday",
404
+ "period_index": 1,
405
+ "room_id": "AI09 3F 08",
406
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
407
+ "faculty_name": "MBA Faculty",
408
+ "subject_code": "BA",
409
+ "section_id": "7B",
410
+ "duration": 1
411
+ },
412
+ "BA-7DS-0": {
413
+ "start_slot": 5,
414
+ "day_index": 0,
415
+ "day_name": "Monday",
416
+ "period_index": 5,
417
+ "room_id": "AI09 2F 13",
418
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
419
+ "faculty_name": "Mr. Yogesh Dixith",
420
+ "subject_code": "BA",
421
+ "section_id": "7DS",
422
+ "duration": 1
423
+ },
424
+ "BA-7DS-1": {
425
+ "start_slot": 31,
426
+ "day_index": 3,
427
+ "day_name": "Thursday",
428
+ "period_index": 7,
429
+ "room_id": "AI09 2F 12",
430
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
431
+ "faculty_name": "Mr. Yogesh Dixith",
432
+ "subject_code": "BA",
433
+ "section_id": "7DS",
434
+ "duration": 1
435
+ },
436
+ "BA-7DS-2": {
437
+ "start_slot": 33,
438
+ "day_index": 4,
439
+ "day_name": "Friday",
440
+ "period_index": 1,
441
+ "room_id": "AI09 2F 12",
442
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
443
+ "faculty_name": "Mr. Yogesh Dixith",
444
+ "subject_code": "BA",
445
+ "section_id": "7DS",
446
+ "duration": 1
447
+ },
448
+ "BDA-7A-0": {
449
+ "start_slot": 5,
450
+ "day_index": 0,
451
+ "day_name": "Monday",
452
+ "period_index": 5,
453
+ "room_id": "AI09 1F 25",
454
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
455
+ "faculty_name": "Ms. Surbhi",
456
+ "subject_code": "BDA",
457
+ "section_id": "7A",
458
+ "duration": 1
459
+ },
460
+ "BDA-7A-1": {
461
+ "start_slot": 31,
462
+ "day_index": 3,
463
+ "day_name": "Thursday",
464
+ "period_index": 7,
465
+ "room_id": "AI09 2F 13",
466
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
467
+ "faculty_name": "Ms. Surbhi",
468
+ "subject_code": "BDA",
469
+ "section_id": "7A",
470
+ "duration": 1
471
+ },
472
+ "BDA-7A-2": {
473
+ "start_slot": 33,
474
+ "day_index": 4,
475
+ "day_name": "Friday",
476
+ "period_index": 1,
477
+ "room_id": "AI09 1F26",
478
+ "room_name": "AI09 1F26 (ECE BLOCK)",
479
+ "faculty_name": "Ms. Surbhi",
480
+ "subject_code": "BDA",
481
+ "section_id": "7A",
482
+ "duration": 1
483
+ },
484
+ "BDA-7B-0": {
485
+ "start_slot": 5,
486
+ "day_index": 0,
487
+ "day_name": "Monday",
488
+ "period_index": 5,
489
+ "room_id": "AI09 2F 12",
490
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
491
+ "faculty_name": "Ms. Surbhi",
492
+ "subject_code": "BDA",
493
+ "section_id": "7B",
494
+ "duration": 1
495
+ },
496
+ "BDA-7B-1": {
497
+ "start_slot": 31,
498
+ "day_index": 3,
499
+ "day_name": "Thursday",
500
+ "period_index": 7,
501
+ "room_id": "AI09 1F 25",
502
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
503
+ "faculty_name": "Ms. Surbhi",
504
+ "subject_code": "BDA",
505
+ "section_id": "7B",
506
+ "duration": 1
507
+ },
508
+ "BDA-7B-2": {
509
+ "start_slot": 33,
510
+ "day_index": 4,
511
+ "day_name": "Friday",
512
+ "period_index": 1,
513
+ "room_id": "AI09 1F 25",
514
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
515
+ "faculty_name": "Ms. Surbhi",
516
+ "subject_code": "BDA",
517
+ "section_id": "7B",
518
+ "duration": 1
519
+ },
520
+ "DL-7DS-0": {
521
+ "start_slot": 5,
522
+ "day_index": 0,
523
+ "day_name": "Monday",
524
+ "period_index": 5,
525
+ "room_id": "AI09 3F 08",
526
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
527
+ "faculty_name": "Mr. Nanda Kumar N",
528
+ "subject_code": "DL",
529
+ "section_id": "7DS",
530
+ "duration": 1
531
+ },
532
+ "DL-7DS-1": {
533
+ "start_slot": 31,
534
+ "day_index": 3,
535
+ "day_name": "Thursday",
536
+ "period_index": 7,
537
+ "room_id": "AI09 3F 01",
538
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
539
+ "faculty_name": "Mr. Nanda Kumar N",
540
+ "subject_code": "DL",
541
+ "section_id": "7DS",
542
+ "duration": 1
543
+ },
544
+ "DL-7DS-2": {
545
+ "start_slot": 33,
546
+ "day_index": 4,
547
+ "day_name": "Friday",
548
+ "period_index": 1,
549
+ "room_id": "AI09 2F 14",
550
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
551
+ "faculty_name": "Mr. Nanda Kumar N",
552
+ "subject_code": "DL",
553
+ "section_id": "7DS",
554
+ "duration": 1
555
+ },
556
+ "OE (Sem 7)-7A-0": {
557
+ "start_slot": 10,
558
+ "day_index": 1,
559
+ "day_name": "Tuesday",
560
+ "period_index": 2,
561
+ "room_id": "AI09 2F 08",
562
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
563
+ "faculty_name": "OE Teacher 1",
564
+ "subject_code": "OE (Sem 7)",
565
+ "section_id": "7A",
566
+ "duration": 1
567
+ },
568
+ "OE (Sem 7)-7A-1": {
569
+ "start_slot": 2,
570
+ "day_index": 0,
571
+ "day_name": "Monday",
572
+ "period_index": 2,
573
+ "room_id": "AI09 3F 08",
574
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
575
+ "faculty_name": "OE Teacher 1",
576
+ "subject_code": "OE (Sem 7)",
577
+ "section_id": "7A",
578
+ "duration": 1
579
+ },
580
+ "OE (Sem 7)-7A-2": {
581
+ "start_slot": 18,
582
+ "day_index": 2,
583
+ "day_name": "Wednesday",
584
+ "period_index": 2,
585
+ "room_id": "AI09 2F 12",
586
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
587
+ "faculty_name": "OE Teacher 1",
588
+ "subject_code": "OE (Sem 7)",
589
+ "section_id": "7A",
590
+ "duration": 1
591
+ },
592
+ "OE (Sem 7)-7B-0": {
593
+ "start_slot": 18,
594
+ "day_index": 2,
595
+ "day_name": "Wednesday",
596
+ "period_index": 2,
597
+ "room_id": "AI09 3F 03",
598
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
599
+ "faculty_name": "OE Teacher 2",
600
+ "subject_code": "OE (Sem 7)",
601
+ "section_id": "7B",
602
+ "duration": 1
603
+ },
604
+ "OE (Sem 7)-7B-1": {
605
+ "start_slot": 10,
606
+ "day_index": 1,
607
+ "day_name": "Tuesday",
608
+ "period_index": 2,
609
+ "room_id": "AI09 1F26",
610
+ "room_name": "AI09 1F26 (ECE BLOCK)",
611
+ "faculty_name": "OE Teacher 2",
612
+ "subject_code": "OE (Sem 7)",
613
+ "section_id": "7B",
614
+ "duration": 1
615
+ },
616
+ "OE (Sem 7)-7B-2": {
617
+ "start_slot": 2,
618
+ "day_index": 0,
619
+ "day_name": "Monday",
620
+ "period_index": 2,
621
+ "room_id": "AI09 3F 03",
622
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
623
+ "faculty_name": "OE Teacher 2",
624
+ "subject_code": "OE (Sem 7)",
625
+ "section_id": "7B",
626
+ "duration": 1
627
+ },
628
+ "OE (Sem 7)-7DS-0": {
629
+ "start_slot": 18,
630
+ "day_index": 2,
631
+ "day_name": "Wednesday",
632
+ "period_index": 2,
633
+ "room_id": "AI09 3F 08",
634
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
635
+ "faculty_name": "OE Teacher 3 (Sem 7)",
636
+ "subject_code": "OE (Sem 7)",
637
+ "section_id": "7DS",
638
+ "duration": 1
639
+ },
640
+ "OE (Sem 7)-7DS-1": {
641
+ "start_slot": 2,
642
+ "day_index": 0,
643
+ "day_name": "Monday",
644
+ "period_index": 2,
645
+ "room_id": "AI09 1F26",
646
+ "room_name": "AI09 1F26 (ECE BLOCK)",
647
+ "faculty_name": "OE Teacher 3 (Sem 7)",
648
+ "subject_code": "OE (Sem 7)",
649
+ "section_id": "7DS",
650
+ "duration": 1
651
+ },
652
+ "OE (Sem 7)-7DS-2": {
653
+ "start_slot": 10,
654
+ "day_index": 1,
655
+ "day_name": "Tuesday",
656
+ "period_index": 2,
657
+ "room_id": "AI09 1F 25",
658
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
659
+ "faculty_name": "OE Teacher 3 (Sem 7)",
660
+ "subject_code": "OE (Sem 7)",
661
+ "section_id": "7DS",
662
+ "duration": 1
663
+ },
664
+ "PP-7DS-0": {
665
+ "start_slot": 16,
666
+ "day_index": 2,
667
+ "day_name": "Wednesday",
668
+ "period_index": 0,
669
+ "room_id": "AI09 1F 25",
670
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
671
+ "faculty_name": "Mr. Chalasani Jayanth",
672
+ "subject_code": "PP",
673
+ "section_id": "7DS",
674
+ "duration": 1
675
+ },
676
+ "PP-7DS-1": {
677
+ "start_slot": 3,
678
+ "day_index": 0,
679
+ "day_name": "Monday",
680
+ "period_index": 3,
681
+ "room_id": "AI09 1F26",
682
+ "room_name": "AI09 1F26 (ECE BLOCK)",
683
+ "faculty_name": "Mr. Chalasani Jayanth",
684
+ "subject_code": "PP",
685
+ "section_id": "7DS",
686
+ "duration": 1
687
+ },
688
+ "PP-7DS-2": {
689
+ "start_slot": 34,
690
+ "day_index": 4,
691
+ "day_name": "Friday",
692
+ "period_index": 2,
693
+ "room_id": "AI09 3F 01",
694
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
695
+ "faculty_name": "Mr. Chalasani Jayanth",
696
+ "subject_code": "PP",
697
+ "section_id": "7DS",
698
+ "duration": 1
699
+ },
700
+ "PP Lab-7DS-B1-BLOCK": {
701
+ "start_slot": 38,
702
+ "day_index": 4,
703
+ "day_name": "Friday",
704
+ "period_index": 6,
705
+ "room_id": "AI09 3F 01",
706
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
707
+ "faculty_name": "Mr. Chalasani Jayanth",
708
+ "subject_code": "PP Lab",
709
+ "section_id": "7DS-B1",
710
+ "duration": 2
711
+ },
712
+ "PP Lab-7DS-B2-BLOCK": {
713
+ "start_slot": 14,
714
+ "day_index": 1,
715
+ "day_name": "Tuesday",
716
+ "period_index": 6,
717
+ "room_id": "AI09 3F 01",
718
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
719
+ "faculty_name": "Mr. Chalasani Jayanth",
720
+ "subject_code": "PP Lab",
721
+ "section_id": "7DS-B2",
722
+ "duration": 2
723
+ },
724
+ "SML for DS-7DS-0": {
725
+ "start_slot": 32,
726
+ "day_index": 4,
727
+ "day_name": "Friday",
728
+ "period_index": 0,
729
+ "room_id": "AI09 3F 01",
730
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
731
+ "faculty_name": "Dr. Kavitha Nair R",
732
+ "subject_code": "SML for DS",
733
+ "section_id": "7DS",
734
+ "duration": 1
735
+ },
736
+ "SML for DS-7DS-1": {
737
+ "start_slot": 0,
738
+ "day_index": 0,
739
+ "day_name": "Monday",
740
+ "period_index": 0,
741
+ "room_id": "AI09 1F26",
742
+ "room_name": "AI09 1F26 (ECE BLOCK)",
743
+ "faculty_name": "Dr. Kavitha Nair R",
744
+ "subject_code": "SML for DS",
745
+ "section_id": "7DS",
746
+ "duration": 1
747
+ },
748
+ "SML for DS-7DS-2": {
749
+ "start_slot": 25,
750
+ "day_index": 3,
751
+ "day_name": "Thursday",
752
+ "period_index": 1,
753
+ "room_id": "AI09 3F 03",
754
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
755
+ "faculty_name": "Dr. Kavitha Nair R",
756
+ "subject_code": "SML for DS",
757
+ "section_id": "7DS",
758
+ "duration": 1
759
+ },
760
+ "SML for DS Lab-7DS-B1-BLOCK": {
761
+ "start_slot": 29,
762
+ "day_index": 3,
763
+ "day_name": "Thursday",
764
+ "period_index": 5,
765
+ "room_id": "AI09 1F 25",
766
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
767
+ "faculty_name": "Dr. Kavitha Nair R",
768
+ "subject_code": "SML for DS Lab",
769
+ "section_id": "7DS-B1",
770
+ "duration": 2
771
+ },
772
+ "SML for DS Lab-7DS-B2-BLOCK": {
773
+ "start_slot": 36,
774
+ "day_index": 4,
775
+ "day_name": "Friday",
776
+ "period_index": 4,
777
+ "room_id": "AI09 2F 13",
778
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
779
+ "faculty_name": "Dr. Kavitha Nair R",
780
+ "subject_code": "SML for DS Lab",
781
+ "section_id": "7DS-B2",
782
+ "duration": 2
783
+ },
784
+ "CNS-7DS-0": {
785
+ "start_slot": 19,
786
+ "day_index": 2,
787
+ "day_name": "Wednesday",
788
+ "period_index": 3,
789
+ "room_id": "AI09 3F 01",
790
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
791
+ "faculty_name": "Mr. Jovin Deglus",
792
+ "subject_code": "CNS",
793
+ "section_id": "7DS",
794
+ "duration": 1
795
+ },
796
+ "CNS-7DS-1": {
797
+ "start_slot": 8,
798
+ "day_index": 1,
799
+ "day_name": "Tuesday",
800
+ "period_index": 0,
801
+ "room_id": "AI09 2F 12",
802
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
803
+ "faculty_name": "Mr. Jovin Deglus",
804
+ "subject_code": "CNS",
805
+ "section_id": "7DS",
806
+ "duration": 1
807
+ },
808
+ "CNS-7DS-2": {
809
+ "start_slot": 1,
810
+ "day_index": 0,
811
+ "day_name": "Monday",
812
+ "period_index": 1,
813
+ "room_id": "AI09 2F 13",
814
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
815
+ "faculty_name": "Mr. Jovin Deglus",
816
+ "subject_code": "CNS",
817
+ "section_id": "7DS",
818
+ "duration": 1
819
+ },
820
+ "CNS-7DS-3": {
821
+ "start_slot": 35,
822
+ "day_index": 4,
823
+ "day_name": "Friday",
824
+ "period_index": 3,
825
+ "room_id": "AI09 1F 25",
826
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
827
+ "faculty_name": "Mr. Jovin Deglus",
828
+ "subject_code": "CNS",
829
+ "section_id": "7DS",
830
+ "duration": 1
831
+ },
832
+ "SEPM-5A-0": {
833
+ "start_slot": 17,
834
+ "day_index": 2,
835
+ "day_name": "Wednesday",
836
+ "period_index": 1,
837
+ "room_id": "AI09 2F 14",
838
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
839
+ "faculty_name": "NF3",
840
+ "subject_code": "SEPM",
841
+ "section_id": "5A",
842
+ "duration": 1
843
+ },
844
+ "SEPM-5A-1": {
845
+ "start_slot": 35,
846
+ "day_index": 4,
847
+ "day_name": "Friday",
848
+ "period_index": 3,
849
+ "room_id": "AI09 3F 03",
850
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
851
+ "faculty_name": "NF3",
852
+ "subject_code": "SEPM",
853
+ "section_id": "5A",
854
+ "duration": 1
855
+ },
856
+ "SEPM-5A-2": {
857
+ "start_slot": 2,
858
+ "day_index": 0,
859
+ "day_name": "Monday",
860
+ "period_index": 2,
861
+ "room_id": "AI09 1F 25",
862
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
863
+ "faculty_name": "NF3",
864
+ "subject_code": "SEPM",
865
+ "section_id": "5A",
866
+ "duration": 1
867
+ },
868
+ "SEPM-5A-3": {
869
+ "start_slot": 25,
870
+ "day_index": 3,
871
+ "day_name": "Thursday",
872
+ "period_index": 1,
873
+ "room_id": "AI09 1F26",
874
+ "room_name": "AI09 1F26 (ECE BLOCK)",
875
+ "faculty_name": "NF3",
876
+ "subject_code": "SEPM",
877
+ "section_id": "5A",
878
+ "duration": 1
879
+ },
880
+ "SEPM-5B-0": {
881
+ "start_slot": 16,
882
+ "day_index": 2,
883
+ "day_name": "Wednesday",
884
+ "period_index": 0,
885
+ "room_id": "AI09 3F 01",
886
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
887
+ "faculty_name": "NF3",
888
+ "subject_code": "SEPM",
889
+ "section_id": "5B",
890
+ "duration": 1
891
+ },
892
+ "SEPM-5B-1": {
893
+ "start_slot": 8,
894
+ "day_index": 1,
895
+ "day_name": "Tuesday",
896
+ "period_index": 0,
897
+ "room_id": "AI09 3F 03",
898
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
899
+ "faculty_name": "NF3",
900
+ "subject_code": "SEPM",
901
+ "section_id": "5B",
902
+ "duration": 1
903
+ },
904
+ "SEPM-5B-2": {
905
+ "start_slot": 1,
906
+ "day_index": 0,
907
+ "day_name": "Monday",
908
+ "period_index": 1,
909
+ "room_id": "AI09 2F 14",
910
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
911
+ "faculty_name": "NF3",
912
+ "subject_code": "SEPM",
913
+ "section_id": "5B",
914
+ "duration": 1
915
+ },
916
+ "SEPM-5B-3": {
917
+ "start_slot": 32,
918
+ "day_index": 4,
919
+ "day_name": "Friday",
920
+ "period_index": 0,
921
+ "room_id": "AI09 2F 13",
922
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
923
+ "faculty_name": "NF3",
924
+ "subject_code": "SEPM",
925
+ "section_id": "5B",
926
+ "duration": 1
927
+ },
928
+ "CN-5A-0": {
929
+ "start_slot": 1,
930
+ "day_index": 0,
931
+ "day_name": "Monday",
932
+ "period_index": 1,
933
+ "room_id": "AI09 3F 01",
934
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
935
+ "faculty_name": "Dr. S Anu Pallavi",
936
+ "subject_code": "CN",
937
+ "section_id": "5A",
938
+ "duration": 1
939
+ },
940
+ "CN-5A-1": {
941
+ "start_slot": 8,
942
+ "day_index": 1,
943
+ "day_name": "Tuesday",
944
+ "period_index": 0,
945
+ "room_id": "AI09 3F 01",
946
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
947
+ "faculty_name": "Dr. S Anu Pallavi",
948
+ "subject_code": "CN",
949
+ "section_id": "5A",
950
+ "duration": 1
951
+ },
952
+ "CN-5A-2": {
953
+ "start_slot": 34,
954
+ "day_index": 4,
955
+ "day_name": "Friday",
956
+ "period_index": 2,
957
+ "room_id": "AI09 2F 08",
958
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
959
+ "faculty_name": "Dr. S Anu Pallavi",
960
+ "subject_code": "CN",
961
+ "section_id": "5A",
962
+ "duration": 1
963
+ },
964
+ "CN-5B-0": {
965
+ "start_slot": 18,
966
+ "day_index": 2,
967
+ "day_name": "Wednesday",
968
+ "period_index": 2,
969
+ "room_id": "AI09 1F 25",
970
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
971
+ "faculty_name": "Dr. S Anu Pallavi",
972
+ "subject_code": "CN",
973
+ "section_id": "5B",
974
+ "duration": 1
975
+ },
976
+ "CN-5B-1": {
977
+ "start_slot": 3,
978
+ "day_index": 0,
979
+ "day_name": "Monday",
980
+ "period_index": 3,
981
+ "room_id": "AI09 3F 08",
982
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
983
+ "faculty_name": "Dr. S Anu Pallavi",
984
+ "subject_code": "CN",
985
+ "section_id": "5B",
986
+ "duration": 1
987
+ },
988
+ "CN-5B-2": {
989
+ "start_slot": 35,
990
+ "day_index": 4,
991
+ "day_name": "Friday",
992
+ "period_index": 3,
993
+ "room_id": "AI09 2F 13",
994
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
995
+ "faculty_name": "Dr. S Anu Pallavi",
996
+ "subject_code": "CN",
997
+ "section_id": "5B",
998
+ "duration": 1
999
+ },
1000
+ "CN Lab-5A-B1-BLOCK": {
1001
+ "start_slot": 12,
1002
+ "day_index": 1,
1003
+ "day_name": "Tuesday",
1004
+ "period_index": 4,
1005
+ "room_id": "AI09 2F 13",
1006
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1007
+ "faculty_name": "Dr. S Anu Pallavi",
1008
+ "subject_code": "CN Lab",
1009
+ "section_id": "5A-B1",
1010
+ "duration": 2
1011
+ },
1012
+ "CN Lab-5A-B2-BLOCK": {
1013
+ "start_slot": 20,
1014
+ "day_index": 2,
1015
+ "day_name": "Wednesday",
1016
+ "period_index": 4,
1017
+ "room_id": "AI09 3F 01",
1018
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1019
+ "faculty_name": "Dr. S Anu Pallavi",
1020
+ "subject_code": "CN Lab",
1021
+ "section_id": "5A-B2",
1022
+ "duration": 2
1023
+ },
1024
+ "CN Lab-5B-B1-BLOCK": {
1025
+ "start_slot": 6,
1026
+ "day_index": 0,
1027
+ "day_name": "Monday",
1028
+ "period_index": 6,
1029
+ "room_id": "AI09 2F 12",
1030
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1031
+ "faculty_name": "Dr. S Anu Pallavi",
1032
+ "subject_code": "CN Lab",
1033
+ "section_id": "5B-B1",
1034
+ "duration": 2
1035
+ },
1036
+ "CN Lab-5B-B2-BLOCK": {
1037
+ "start_slot": 26,
1038
+ "day_index": 3,
1039
+ "day_name": "Thursday",
1040
+ "period_index": 2,
1041
+ "room_id": "AI09 2F 14",
1042
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1043
+ "faculty_name": "Dr. S Anu Pallavi",
1044
+ "subject_code": "CN Lab",
1045
+ "section_id": "5B-B2",
1046
+ "duration": 2
1047
+ },
1048
+ "TOC-5A-0": {
1049
+ "start_slot": 18,
1050
+ "day_index": 2,
1051
+ "day_name": "Wednesday",
1052
+ "period_index": 2,
1053
+ "room_id": "AI09 2F 14",
1054
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1055
+ "faculty_name": "Ms. Vinutha M",
1056
+ "subject_code": "TOC",
1057
+ "section_id": "5A",
1058
+ "duration": 1
1059
+ },
1060
+ "TOC-5A-1": {
1061
+ "start_slot": 3,
1062
+ "day_index": 0,
1063
+ "day_name": "Monday",
1064
+ "period_index": 3,
1065
+ "room_id": "AI09 2F 12",
1066
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1067
+ "faculty_name": "Ms. Vinutha M",
1068
+ "subject_code": "TOC",
1069
+ "section_id": "5A",
1070
+ "duration": 1
1071
+ },
1072
+ "TOC-5A-2": {
1073
+ "start_slot": 11,
1074
+ "day_index": 1,
1075
+ "day_name": "Tuesday",
1076
+ "period_index": 3,
1077
+ "room_id": "AI09 2F 14",
1078
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1079
+ "faculty_name": "Ms. Vinutha M",
1080
+ "subject_code": "TOC",
1081
+ "section_id": "5A",
1082
+ "duration": 1
1083
+ },
1084
+ "TOC-5A-3": {
1085
+ "start_slot": 27,
1086
+ "day_index": 3,
1087
+ "day_name": "Thursday",
1088
+ "period_index": 3,
1089
+ "room_id": "AI09 2F 13",
1090
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1091
+ "faculty_name": "Ms. Vinutha M",
1092
+ "subject_code": "TOC",
1093
+ "section_id": "5A",
1094
+ "duration": 1
1095
+ },
1096
+ "TOC-5A-4": {
1097
+ "start_slot": 32,
1098
+ "day_index": 4,
1099
+ "day_name": "Friday",
1100
+ "period_index": 0,
1101
+ "room_id": "AI09 2F 08",
1102
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1103
+ "faculty_name": "Ms. Vinutha M",
1104
+ "subject_code": "TOC",
1105
+ "section_id": "5A",
1106
+ "duration": 1
1107
+ },
1108
+ "TOC-5B-0": {
1109
+ "start_slot": 25,
1110
+ "day_index": 3,
1111
+ "day_name": "Thursday",
1112
+ "period_index": 1,
1113
+ "room_id": "AI09 1F 25",
1114
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1115
+ "faculty_name": "Ms. Vinutha M",
1116
+ "subject_code": "TOC",
1117
+ "section_id": "5B",
1118
+ "duration": 1
1119
+ },
1120
+ "TOC-5B-1": {
1121
+ "start_slot": 10,
1122
+ "day_index": 1,
1123
+ "day_name": "Tuesday",
1124
+ "period_index": 2,
1125
+ "room_id": "AI09 3F 08",
1126
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
1127
+ "faculty_name": "Ms. Vinutha M",
1128
+ "subject_code": "TOC",
1129
+ "section_id": "5B",
1130
+ "duration": 1
1131
+ },
1132
+ "TOC-5B-2": {
1133
+ "start_slot": 2,
1134
+ "day_index": 0,
1135
+ "day_name": "Monday",
1136
+ "period_index": 2,
1137
+ "room_id": "AI09 3F 01",
1138
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1139
+ "faculty_name": "Ms. Vinutha M",
1140
+ "subject_code": "TOC",
1141
+ "section_id": "5B",
1142
+ "duration": 1
1143
+ },
1144
+ "TOC-5B-3": {
1145
+ "start_slot": 17,
1146
+ "day_index": 2,
1147
+ "day_name": "Wednesday",
1148
+ "period_index": 1,
1149
+ "room_id": "AI09 3F 01",
1150
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1151
+ "faculty_name": "Ms. Vinutha M",
1152
+ "subject_code": "TOC",
1153
+ "section_id": "5B",
1154
+ "duration": 1
1155
+ },
1156
+ "TOC-5B-4": {
1157
+ "start_slot": 34,
1158
+ "day_index": 4,
1159
+ "day_name": "Friday",
1160
+ "period_index": 2,
1161
+ "room_id": "AI09 2F 13",
1162
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1163
+ "faculty_name": "Ms. Vinutha M",
1164
+ "subject_code": "TOC",
1165
+ "section_id": "5B",
1166
+ "duration": 1
1167
+ },
1168
+ "DV Lab-5B-B1-BLOCK": {
1169
+ "start_slot": 36,
1170
+ "day_index": 4,
1171
+ "day_name": "Friday",
1172
+ "period_index": 4,
1173
+ "room_id": "AI03 2F 01",
1174
+ "room_name": "AI03 2F 01 (ASD BLOCK)",
1175
+ "faculty_name": "Jovin Deglus / Ranjan Kumar",
1176
+ "subject_code": "DV Lab",
1177
+ "section_id": "5B-B1",
1178
+ "duration": 2
1179
+ },
1180
+ "DV Lab-5B-B2-BLOCK": {
1181
+ "start_slot": 12,
1182
+ "day_index": 1,
1183
+ "day_name": "Tuesday",
1184
+ "period_index": 4,
1185
+ "room_id": "AI03 2F 01",
1186
+ "room_name": "AI03 2F 01 (ASD BLOCK)",
1187
+ "faculty_name": "Jovin Deglus / Ranjan Kumar",
1188
+ "subject_code": "DV Lab",
1189
+ "section_id": "5B-B2",
1190
+ "duration": 2
1191
+ },
1192
+ "CV-5A-0": {
1193
+ "start_slot": 14,
1194
+ "day_index": 1,
1195
+ "day_name": "Tuesday",
1196
+ "period_index": 6,
1197
+ "room_id": "AI09 2F 14",
1198
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1199
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1200
+ "subject_code": "CV",
1201
+ "section_id": "5A",
1202
+ "duration": 1
1203
+ },
1204
+ "CV-5A-1": {
1205
+ "start_slot": 4,
1206
+ "day_index": 0,
1207
+ "day_name": "Monday",
1208
+ "period_index": 4,
1209
+ "room_id": "AI09 3F 08",
1210
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
1211
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1212
+ "subject_code": "CV",
1213
+ "section_id": "5A",
1214
+ "duration": 1
1215
+ },
1216
+ "CV-5A-2": {
1217
+ "start_slot": 23,
1218
+ "day_index": 2,
1219
+ "day_name": "Wednesday",
1220
+ "period_index": 7,
1221
+ "room_id": "AI09 1F26",
1222
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1223
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1224
+ "subject_code": "CV",
1225
+ "section_id": "5A",
1226
+ "duration": 1
1227
+ },
1228
+ "EDA-5A-0": {
1229
+ "start_slot": 14,
1230
+ "day_index": 1,
1231
+ "day_name": "Tuesday",
1232
+ "period_index": 6,
1233
+ "room_id": "AI09 2F 12",
1234
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1235
+ "faculty_name": "Ms. Surbhi",
1236
+ "subject_code": "EDA",
1237
+ "section_id": "5A",
1238
+ "duration": 1
1239
+ },
1240
+ "EDA-5A-1": {
1241
+ "start_slot": 4,
1242
+ "day_index": 0,
1243
+ "day_name": "Monday",
1244
+ "period_index": 4,
1245
+ "room_id": "AI09 2F 14",
1246
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1247
+ "faculty_name": "Ms. Surbhi",
1248
+ "subject_code": "EDA",
1249
+ "section_id": "5A",
1250
+ "duration": 1
1251
+ },
1252
+ "EDA-5A-2": {
1253
+ "start_slot": 23,
1254
+ "day_index": 2,
1255
+ "day_name": "Wednesday",
1256
+ "period_index": 7,
1257
+ "room_id": "AI09 3F 03",
1258
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1259
+ "faculty_name": "Ms. Surbhi",
1260
+ "subject_code": "EDA",
1261
+ "section_id": "5A",
1262
+ "duration": 1
1263
+ },
1264
+ "CV-5B-0": {
1265
+ "start_slot": 14,
1266
+ "day_index": 1,
1267
+ "day_name": "Tuesday",
1268
+ "period_index": 6,
1269
+ "room_id": "AI09 2F 08",
1270
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1271
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1272
+ "subject_code": "CV",
1273
+ "section_id": "5B",
1274
+ "duration": 1
1275
+ },
1276
+ "CV-5B-1": {
1277
+ "start_slot": 4,
1278
+ "day_index": 0,
1279
+ "day_name": "Monday",
1280
+ "period_index": 4,
1281
+ "room_id": "AI09 1F 25",
1282
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1283
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1284
+ "subject_code": "CV",
1285
+ "section_id": "5B",
1286
+ "duration": 1
1287
+ },
1288
+ "CV-5B-2": {
1289
+ "start_slot": 23,
1290
+ "day_index": 2,
1291
+ "day_name": "Wednesday",
1292
+ "period_index": 7,
1293
+ "room_id": "AI09 3F 08",
1294
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
1295
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1296
+ "subject_code": "CV",
1297
+ "section_id": "5B",
1298
+ "duration": 1
1299
+ },
1300
+ "EDA-5B-0": {
1301
+ "start_slot": 14,
1302
+ "day_index": 1,
1303
+ "day_name": "Tuesday",
1304
+ "period_index": 6,
1305
+ "room_id": "AI09 1F26",
1306
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1307
+ "faculty_name": "Ms. Surbhi",
1308
+ "subject_code": "EDA",
1309
+ "section_id": "5B",
1310
+ "duration": 1
1311
+ },
1312
+ "EDA-5B-1": {
1313
+ "start_slot": 4,
1314
+ "day_index": 0,
1315
+ "day_name": "Monday",
1316
+ "period_index": 4,
1317
+ "room_id": "AI09 2F 13",
1318
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1319
+ "faculty_name": "Ms. Surbhi",
1320
+ "subject_code": "EDA",
1321
+ "section_id": "5B",
1322
+ "duration": 1
1323
+ },
1324
+ "EDA-5B-2": {
1325
+ "start_slot": 23,
1326
+ "day_index": 2,
1327
+ "day_name": "Wednesday",
1328
+ "period_index": 7,
1329
+ "room_id": "AI09 2F 13",
1330
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1331
+ "faculty_name": "Ms. Surbhi",
1332
+ "subject_code": "EDA",
1333
+ "section_id": "5B",
1334
+ "duration": 1
1335
+ },
1336
+ "RM&IPR-5A-0": {
1337
+ "start_slot": 37,
1338
+ "day_index": 4,
1339
+ "day_name": "Friday",
1340
+ "period_index": 5,
1341
+ "room_id": "AI09 2F 14",
1342
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1343
+ "faculty_name": "Basavaraj",
1344
+ "subject_code": "RM&IPR",
1345
+ "section_id": "5A",
1346
+ "duration": 1
1347
+ },
1348
+ "RM&IPR-5B-0": {
1349
+ "start_slot": 28,
1350
+ "day_index": 3,
1351
+ "day_name": "Thursday",
1352
+ "period_index": 4,
1353
+ "room_id": "AI09 1F26",
1354
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1355
+ "faculty_name": "Basavaraj",
1356
+ "subject_code": "RM&IPR",
1357
+ "section_id": "5B",
1358
+ "duration": 1
1359
+ },
1360
+ "EVS-5A-0": {
1361
+ "start_slot": 5,
1362
+ "day_index": 0,
1363
+ "day_name": "Monday",
1364
+ "period_index": 5,
1365
+ "room_id": "AI09 3F 03",
1366
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1367
+ "faculty_name": "CIVIL_1",
1368
+ "subject_code": "EVS",
1369
+ "section_id": "5A",
1370
+ "duration": 1
1371
+ },
1372
+ "EVS-5A-1": {
1373
+ "start_slot": 36,
1374
+ "day_index": 4,
1375
+ "day_name": "Friday",
1376
+ "period_index": 4,
1377
+ "room_id": "AI09 3F 01",
1378
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1379
+ "faculty_name": "CIVIL_1",
1380
+ "subject_code": "EVS",
1381
+ "section_id": "5A",
1382
+ "duration": 1
1383
+ },
1384
+ "EVS-5B-0": {
1385
+ "start_slot": 22,
1386
+ "day_index": 2,
1387
+ "day_name": "Wednesday",
1388
+ "period_index": 6,
1389
+ "room_id": "AI09 1F26",
1390
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1391
+ "faculty_name": "CIVIL_1",
1392
+ "subject_code": "EVS",
1393
+ "section_id": "5B",
1394
+ "duration": 1
1395
+ },
1396
+ "EVS-5B-1": {
1397
+ "start_slot": 31,
1398
+ "day_index": 3,
1399
+ "day_name": "Thursday",
1400
+ "period_index": 7,
1401
+ "room_id": "AI09 1F26",
1402
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1403
+ "faculty_name": "CIVIL_1",
1404
+ "subject_code": "EVS",
1405
+ "section_id": "5B",
1406
+ "duration": 1
1407
+ },
1408
+ "SEPM-5DS-0": {
1409
+ "start_slot": 17,
1410
+ "day_index": 2,
1411
+ "day_name": "Wednesday",
1412
+ "period_index": 1,
1413
+ "room_id": "AI09 2F 13",
1414
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1415
+ "faculty_name": "Mr. Ranjan Kumar",
1416
+ "subject_code": "SEPM",
1417
+ "section_id": "5DS",
1418
+ "duration": 1
1419
+ },
1420
+ "SEPM-5DS-1": {
1421
+ "start_slot": 8,
1422
+ "day_index": 1,
1423
+ "day_name": "Tuesday",
1424
+ "period_index": 0,
1425
+ "room_id": "AI09 1F 25",
1426
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1427
+ "faculty_name": "Mr. Ranjan Kumar",
1428
+ "subject_code": "SEPM",
1429
+ "section_id": "5DS",
1430
+ "duration": 1
1431
+ },
1432
+ "SEPM-5DS-2": {
1433
+ "start_slot": 3,
1434
+ "day_index": 0,
1435
+ "day_name": "Monday",
1436
+ "period_index": 3,
1437
+ "room_id": "AI09 2F 13",
1438
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1439
+ "faculty_name": "Mr. Ranjan Kumar",
1440
+ "subject_code": "SEPM",
1441
+ "section_id": "5DS",
1442
+ "duration": 1
1443
+ },
1444
+ "SEPM-5DS-3": {
1445
+ "start_slot": 25,
1446
+ "day_index": 3,
1447
+ "day_name": "Thursday",
1448
+ "period_index": 1,
1449
+ "room_id": "AI09 2F 12",
1450
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1451
+ "faculty_name": "Mr. Ranjan Kumar",
1452
+ "subject_code": "SEPM",
1453
+ "section_id": "5DS",
1454
+ "duration": 1
1455
+ },
1456
+ "CN-5DS-0": {
1457
+ "start_slot": 16,
1458
+ "day_index": 2,
1459
+ "day_name": "Wednesday",
1460
+ "period_index": 0,
1461
+ "room_id": "AI09 2F 12",
1462
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1463
+ "faculty_name": "Mr. Nanda Kumar N",
1464
+ "subject_code": "CN",
1465
+ "section_id": "5DS",
1466
+ "duration": 1
1467
+ },
1468
+ "CN-5DS-1": {
1469
+ "start_slot": 2,
1470
+ "day_index": 0,
1471
+ "day_name": "Monday",
1472
+ "period_index": 2,
1473
+ "room_id": "AI09 2F 12",
1474
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1475
+ "faculty_name": "Mr. Nanda Kumar N",
1476
+ "subject_code": "CN",
1477
+ "section_id": "5DS",
1478
+ "duration": 1
1479
+ },
1480
+ "CN-5DS-2": {
1481
+ "start_slot": 34,
1482
+ "day_index": 4,
1483
+ "day_name": "Friday",
1484
+ "period_index": 2,
1485
+ "room_id": "AI09 3F 03",
1486
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1487
+ "faculty_name": "Mr. Nanda Kumar N",
1488
+ "subject_code": "CN",
1489
+ "section_id": "5DS",
1490
+ "duration": 1
1491
+ },
1492
+ "CN Lab-5DS-B1-BLOCK": {
1493
+ "start_slot": 22,
1494
+ "day_index": 2,
1495
+ "day_name": "Wednesday",
1496
+ "period_index": 6,
1497
+ "room_id": "AI09 2F 12",
1498
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1499
+ "faculty_name": "Mr. Nanda Kumar N",
1500
+ "subject_code": "CN Lab",
1501
+ "section_id": "5DS-B1",
1502
+ "duration": 2
1503
+ },
1504
+ "CN Lab-5DS-B2-BLOCK": {
1505
+ "start_slot": 38,
1506
+ "day_index": 4,
1507
+ "day_name": "Friday",
1508
+ "period_index": 6,
1509
+ "room_id": "AI09 1F 25",
1510
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1511
+ "faculty_name": "Mr. Nanda Kumar N",
1512
+ "subject_code": "CN Lab",
1513
+ "section_id": "5DS-B2",
1514
+ "duration": 2
1515
+ },
1516
+ "TOC-5DS-0": {
1517
+ "start_slot": 24,
1518
+ "day_index": 3,
1519
+ "day_name": "Thursday",
1520
+ "period_index": 0,
1521
+ "room_id": "AI09 1F26",
1522
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1523
+ "faculty_name": "Ms. Vinutha M",
1524
+ "subject_code": "TOC",
1525
+ "section_id": "5DS",
1526
+ "duration": 1
1527
+ },
1528
+ "TOC-5DS-1": {
1529
+ "start_slot": 19,
1530
+ "day_index": 2,
1531
+ "day_name": "Wednesday",
1532
+ "period_index": 3,
1533
+ "room_id": "AI09 3F 03",
1534
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1535
+ "faculty_name": "Ms. Vinutha M",
1536
+ "subject_code": "TOC",
1537
+ "section_id": "5DS",
1538
+ "duration": 1
1539
+ },
1540
+ "TOC-5DS-2": {
1541
+ "start_slot": 9,
1542
+ "day_index": 1,
1543
+ "day_name": "Tuesday",
1544
+ "period_index": 1,
1545
+ "room_id": "AI09 1F26",
1546
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1547
+ "faculty_name": "Ms. Vinutha M",
1548
+ "subject_code": "TOC",
1549
+ "section_id": "5DS",
1550
+ "duration": 1
1551
+ },
1552
+ "TOC-5DS-3": {
1553
+ "start_slot": 0,
1554
+ "day_index": 0,
1555
+ "day_name": "Monday",
1556
+ "period_index": 0,
1557
+ "room_id": "AI09 2F 14",
1558
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1559
+ "faculty_name": "Ms. Vinutha M",
1560
+ "subject_code": "TOC",
1561
+ "section_id": "5DS",
1562
+ "duration": 1
1563
+ },
1564
+ "TOC-5DS-4": {
1565
+ "start_slot": 35,
1566
+ "day_index": 4,
1567
+ "day_name": "Friday",
1568
+ "period_index": 3,
1569
+ "room_id": "AI09 2F 08",
1570
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1571
+ "faculty_name": "Ms. Vinutha M",
1572
+ "subject_code": "TOC",
1573
+ "section_id": "5DS",
1574
+ "duration": 1
1575
+ },
1576
+ "DV Lab-5DS-B1-BLOCK": {
1577
+ "start_slot": 20,
1578
+ "day_index": 2,
1579
+ "day_name": "Wednesday",
1580
+ "period_index": 4,
1581
+ "room_id": "AI03 2F 01",
1582
+ "room_name": "AI03 2F 01 (ASD BLOCK)",
1583
+ "faculty_name": "Syed Musadiq Illahi / Chalasani Jayanth",
1584
+ "subject_code": "DV Lab",
1585
+ "section_id": "5DS-B1",
1586
+ "duration": 2
1587
+ },
1588
+ "DV Lab-5DS-B2-BLOCK": {
1589
+ "start_slot": 26,
1590
+ "day_index": 3,
1591
+ "day_name": "Thursday",
1592
+ "period_index": 2,
1593
+ "room_id": "AI03 2F 02",
1594
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
1595
+ "faculty_name": "Praveen Arokia Raj / Chalasani Jayanth",
1596
+ "subject_code": "DV Lab",
1597
+ "section_id": "5DS-B2",
1598
+ "duration": 2
1599
+ },
1600
+ "NoSQL DB-5DS-0": {
1601
+ "start_slot": 13,
1602
+ "day_index": 1,
1603
+ "day_name": "Tuesday",
1604
+ "period_index": 5,
1605
+ "room_id": "AI09 3F 03",
1606
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1607
+ "faculty_name": "Mr. Mhd Tahir Mirji",
1608
+ "subject_code": "NoSQL DB",
1609
+ "section_id": "5DS",
1610
+ "duration": 1
1611
+ },
1612
+ "NoSQL DB-5DS-1": {
1613
+ "start_slot": 4,
1614
+ "day_index": 0,
1615
+ "day_name": "Monday",
1616
+ "period_index": 4,
1617
+ "room_id": "AI09 2F 12",
1618
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1619
+ "faculty_name": "Mr. Mhd Tahir Mirji",
1620
+ "subject_code": "NoSQL DB",
1621
+ "section_id": "5DS",
1622
+ "duration": 1
1623
+ },
1624
+ "NoSQL DB-5DS-2": {
1625
+ "start_slot": 36,
1626
+ "day_index": 4,
1627
+ "day_name": "Friday",
1628
+ "period_index": 4,
1629
+ "room_id": "AI09 1F26",
1630
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1631
+ "faculty_name": "Mr. Mhd Tahir Mirji",
1632
+ "subject_code": "NoSQL DB",
1633
+ "section_id": "5DS",
1634
+ "duration": 1
1635
+ },
1636
+ "DW-5DS-0": {
1637
+ "start_slot": 13,
1638
+ "day_index": 1,
1639
+ "day_name": "Tuesday",
1640
+ "period_index": 5,
1641
+ "room_id": "AI09 1F 25",
1642
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1643
+ "faculty_name": "Mr. Abhijith S",
1644
+ "subject_code": "DW",
1645
+ "section_id": "5DS",
1646
+ "duration": 1
1647
+ },
1648
+ "DW-5DS-1": {
1649
+ "start_slot": 4,
1650
+ "day_index": 0,
1651
+ "day_name": "Monday",
1652
+ "period_index": 4,
1653
+ "room_id": "AI09 3F 01",
1654
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1655
+ "faculty_name": "Mr. Abhijith S",
1656
+ "subject_code": "DW",
1657
+ "section_id": "5DS",
1658
+ "duration": 1
1659
+ },
1660
+ "DW-5DS-2": {
1661
+ "start_slot": 36,
1662
+ "day_index": 4,
1663
+ "day_name": "Friday",
1664
+ "period_index": 4,
1665
+ "room_id": "AI09 3F 03",
1666
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1667
+ "faculty_name": "Mr. Abhijith S",
1668
+ "subject_code": "DW",
1669
+ "section_id": "5DS",
1670
+ "duration": 1
1671
+ },
1672
+ "RM&IPR-5DS-0": {
1673
+ "start_slot": 29,
1674
+ "day_index": 3,
1675
+ "day_name": "Thursday",
1676
+ "period_index": 5,
1677
+ "room_id": "AI09 1F26",
1678
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1679
+ "faculty_name": "Basavaraj",
1680
+ "subject_code": "RM&IPR",
1681
+ "section_id": "5DS",
1682
+ "duration": 1
1683
+ },
1684
+ "EVS-5DS-0": {
1685
+ "start_slot": 7,
1686
+ "day_index": 0,
1687
+ "day_name": "Monday",
1688
+ "period_index": 7,
1689
+ "room_id": "AI09 2F 14",
1690
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1691
+ "faculty_name": "CIVIL_1",
1692
+ "subject_code": "EVS",
1693
+ "section_id": "5DS",
1694
+ "duration": 1
1695
+ },
1696
+ "EVS-5DS-1": {
1697
+ "start_slot": 30,
1698
+ "day_index": 3,
1699
+ "day_name": "Thursday",
1700
+ "period_index": 6,
1701
+ "room_id": "AI09 1F26",
1702
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1703
+ "faculty_name": "CIVIL_1",
1704
+ "subject_code": "EVS",
1705
+ "section_id": "5DS",
1706
+ "duration": 1
1707
+ },
1708
+ "Maths (Prob & Stats)-3A-0": {
1709
+ "start_slot": 25,
1710
+ "day_index": 3,
1711
+ "day_name": "Thursday",
1712
+ "period_index": 1,
1713
+ "room_id": "AI09 3F 01",
1714
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1715
+ "faculty_name": "Maths Department",
1716
+ "subject_code": "Maths (Prob & Stats)",
1717
+ "section_id": "3A",
1718
+ "duration": 1
1719
+ },
1720
+ "Maths (Prob & Stats)-3A-1": {
1721
+ "start_slot": 16,
1722
+ "day_index": 2,
1723
+ "day_name": "Wednesday",
1724
+ "period_index": 0,
1725
+ "room_id": "AI09 2F 13",
1726
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1727
+ "faculty_name": "Maths Department",
1728
+ "subject_code": "Maths (Prob & Stats)",
1729
+ "section_id": "3A",
1730
+ "duration": 1
1731
+ },
1732
+ "Maths (Prob & Stats)-3A-2": {
1733
+ "start_slot": 1,
1734
+ "day_index": 0,
1735
+ "day_name": "Monday",
1736
+ "period_index": 1,
1737
+ "room_id": "AI09 2F 12",
1738
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1739
+ "faculty_name": "Maths Department",
1740
+ "subject_code": "Maths (Prob & Stats)",
1741
+ "section_id": "3A",
1742
+ "duration": 1
1743
+ },
1744
+ "Maths (Prob & Stats)-3A-3": {
1745
+ "start_slot": 8,
1746
+ "day_index": 1,
1747
+ "day_name": "Tuesday",
1748
+ "period_index": 0,
1749
+ "room_id": "AI09 2F 14",
1750
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1751
+ "faculty_name": "Maths Department",
1752
+ "subject_code": "Maths (Prob & Stats)",
1753
+ "section_id": "3A",
1754
+ "duration": 1
1755
+ },
1756
+ "Maths (Prob & Stats)-3A-4": {
1757
+ "start_slot": 34,
1758
+ "day_index": 4,
1759
+ "day_name": "Friday",
1760
+ "period_index": 2,
1761
+ "room_id": "AI09 2F 14",
1762
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1763
+ "faculty_name": "Maths Department",
1764
+ "subject_code": "Maths (Prob & Stats)",
1765
+ "section_id": "3A",
1766
+ "duration": 1
1767
+ },
1768
+ "Maths (Prob & Stats)-3B-0": {
1769
+ "start_slot": 26,
1770
+ "day_index": 3,
1771
+ "day_name": "Thursday",
1772
+ "period_index": 2,
1773
+ "room_id": "AI09 2F 13",
1774
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1775
+ "faculty_name": "Maths Department",
1776
+ "subject_code": "Maths (Prob & Stats)",
1777
+ "section_id": "3B",
1778
+ "duration": 1
1779
+ },
1780
+ "Maths (Prob & Stats)-3B-1": {
1781
+ "start_slot": 19,
1782
+ "day_index": 2,
1783
+ "day_name": "Wednesday",
1784
+ "period_index": 3,
1785
+ "room_id": "AI09 2F 14",
1786
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1787
+ "faculty_name": "Maths Department",
1788
+ "subject_code": "Maths (Prob & Stats)",
1789
+ "section_id": "3B",
1790
+ "duration": 1
1791
+ },
1792
+ "Maths (Prob & Stats)-3B-2": {
1793
+ "start_slot": 9,
1794
+ "day_index": 1,
1795
+ "day_name": "Tuesday",
1796
+ "period_index": 1,
1797
+ "room_id": "AI09 3F 03",
1798
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1799
+ "faculty_name": "Maths Department",
1800
+ "subject_code": "Maths (Prob & Stats)",
1801
+ "section_id": "3B",
1802
+ "duration": 1
1803
+ },
1804
+ "Maths (Prob & Stats)-3B-3": {
1805
+ "start_slot": 2,
1806
+ "day_index": 0,
1807
+ "day_name": "Monday",
1808
+ "period_index": 2,
1809
+ "room_id": "AI09 2F 08",
1810
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1811
+ "faculty_name": "Maths Department",
1812
+ "subject_code": "Maths (Prob & Stats)",
1813
+ "section_id": "3B",
1814
+ "duration": 1
1815
+ },
1816
+ "Maths (Prob & Stats)-3B-4": {
1817
+ "start_slot": 35,
1818
+ "day_index": 4,
1819
+ "day_name": "Friday",
1820
+ "period_index": 3,
1821
+ "room_id": "AI09 2F 12",
1822
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1823
+ "faculty_name": "Maths Department",
1824
+ "subject_code": "Maths (Prob & Stats)",
1825
+ "section_id": "3B",
1826
+ "duration": 1
1827
+ },
1828
+ "DDCO-3A-0": {
1829
+ "start_slot": 2,
1830
+ "day_index": 0,
1831
+ "day_name": "Monday",
1832
+ "period_index": 2,
1833
+ "room_id": "AI09 2F 13",
1834
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1835
+ "faculty_name": "Mr. Ranjan Kumar",
1836
+ "subject_code": "DDCO",
1837
+ "section_id": "3A",
1838
+ "duration": 1
1839
+ },
1840
+ "DDCO-3A-1": {
1841
+ "start_slot": 19,
1842
+ "day_index": 2,
1843
+ "day_name": "Wednesday",
1844
+ "period_index": 3,
1845
+ "room_id": "AI09 3F 08",
1846
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
1847
+ "faculty_name": "Mr. Ranjan Kumar",
1848
+ "subject_code": "DDCO",
1849
+ "section_id": "3A",
1850
+ "duration": 1
1851
+ },
1852
+ "DDCO-3A-2": {
1853
+ "start_slot": 24,
1854
+ "day_index": 3,
1855
+ "day_name": "Thursday",
1856
+ "period_index": 0,
1857
+ "room_id": "AI09 3F 01",
1858
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1859
+ "faculty_name": "Mr. Ranjan Kumar",
1860
+ "subject_code": "DDCO",
1861
+ "section_id": "3A",
1862
+ "duration": 1
1863
+ },
1864
+ "DDCO-3A-3": {
1865
+ "start_slot": 9,
1866
+ "day_index": 1,
1867
+ "day_name": "Tuesday",
1868
+ "period_index": 1,
1869
+ "room_id": "AI09 2F 12",
1870
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1871
+ "faculty_name": "Mr. Ranjan Kumar",
1872
+ "subject_code": "DDCO",
1873
+ "section_id": "3A",
1874
+ "duration": 1
1875
+ },
1876
+ "DDCO-3A-4": {
1877
+ "start_slot": 33,
1878
+ "day_index": 4,
1879
+ "day_name": "Friday",
1880
+ "period_index": 1,
1881
+ "room_id": "AI09 3F 03",
1882
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1883
+ "faculty_name": "Mr. Ranjan Kumar",
1884
+ "subject_code": "DDCO",
1885
+ "section_id": "3A",
1886
+ "duration": 1
1887
+ },
1888
+ "DDCO-3B-0": {
1889
+ "start_slot": 27,
1890
+ "day_index": 3,
1891
+ "day_name": "Thursday",
1892
+ "period_index": 3,
1893
+ "room_id": "AI09 3F 08",
1894
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
1895
+ "faculty_name": "Mr. Ranjan Kumar",
1896
+ "subject_code": "DDCO",
1897
+ "section_id": "3B",
1898
+ "duration": 1
1899
+ },
1900
+ "DDCO-3B-1": {
1901
+ "start_slot": 18,
1902
+ "day_index": 2,
1903
+ "day_name": "Wednesday",
1904
+ "period_index": 2,
1905
+ "room_id": "AI09 2F 08",
1906
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1907
+ "faculty_name": "Mr. Ranjan Kumar",
1908
+ "subject_code": "DDCO",
1909
+ "section_id": "3B",
1910
+ "duration": 1
1911
+ },
1912
+ "DDCO-3B-2": {
1913
+ "start_slot": 10,
1914
+ "day_index": 1,
1915
+ "day_name": "Tuesday",
1916
+ "period_index": 2,
1917
+ "room_id": "AI09 3F 03",
1918
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1919
+ "faculty_name": "Mr. Ranjan Kumar",
1920
+ "subject_code": "DDCO",
1921
+ "section_id": "3B",
1922
+ "duration": 1
1923
+ },
1924
+ "DDCO-3B-3": {
1925
+ "start_slot": 0,
1926
+ "day_index": 0,
1927
+ "day_name": "Monday",
1928
+ "period_index": 0,
1929
+ "room_id": "AI09 3F 03",
1930
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1931
+ "faculty_name": "Mr. Ranjan Kumar",
1932
+ "subject_code": "DDCO",
1933
+ "section_id": "3B",
1934
+ "duration": 1
1935
+ },
1936
+ "DDCO-3B-4": {
1937
+ "start_slot": 34,
1938
+ "day_index": 4,
1939
+ "day_name": "Friday",
1940
+ "period_index": 2,
1941
+ "room_id": "AI09 1F 25",
1942
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1943
+ "faculty_name": "Mr. Ranjan Kumar",
1944
+ "subject_code": "DDCO",
1945
+ "section_id": "3B",
1946
+ "duration": 1
1947
+ },
1948
+ "OS-3A-0": {
1949
+ "start_slot": 18,
1950
+ "day_index": 2,
1951
+ "day_name": "Wednesday",
1952
+ "period_index": 2,
1953
+ "room_id": "AI09 2F 13",
1954
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1955
+ "faculty_name": "Mr. Sanjay P",
1956
+ "subject_code": "OS",
1957
+ "section_id": "3A",
1958
+ "duration": 1
1959
+ },
1960
+ "OS-3A-1": {
1961
+ "start_slot": 0,
1962
+ "day_index": 0,
1963
+ "day_name": "Monday",
1964
+ "period_index": 0,
1965
+ "room_id": "AI09 2F 13",
1966
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1967
+ "faculty_name": "Mr. Sanjay P",
1968
+ "subject_code": "OS",
1969
+ "section_id": "3A",
1970
+ "duration": 1
1971
+ },
1972
+ "OS-3A-2": {
1973
+ "start_slot": 32,
1974
+ "day_index": 4,
1975
+ "day_name": "Friday",
1976
+ "period_index": 0,
1977
+ "room_id": "AI09 3F 03",
1978
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1979
+ "faculty_name": "Mr. Sanjay P",
1980
+ "subject_code": "OS",
1981
+ "section_id": "3A",
1982
+ "duration": 1
1983
+ },
1984
+ "OS-3B-0": {
1985
+ "start_slot": 16,
1986
+ "day_index": 2,
1987
+ "day_name": "Wednesday",
1988
+ "period_index": 0,
1989
+ "room_id": "AI09 2F 08",
1990
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1991
+ "faculty_name": "Ms. Vinutha M",
1992
+ "subject_code": "OS",
1993
+ "section_id": "3B",
1994
+ "duration": 1
1995
+ },
1996
+ "OS-3B-1": {
1997
+ "start_slot": 1,
1998
+ "day_index": 0,
1999
+ "day_name": "Monday",
2000
+ "period_index": 1,
2001
+ "room_id": "AI09 1F 25",
2002
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
2003
+ "faculty_name": "Ms. Vinutha M",
2004
+ "subject_code": "OS",
2005
+ "section_id": "3B",
2006
+ "duration": 1
2007
+ },
2008
+ "OS-3B-2": {
2009
+ "start_slot": 33,
2010
+ "day_index": 4,
2011
+ "day_name": "Friday",
2012
+ "period_index": 1,
2013
+ "room_id": "AI09 2F 13",
2014
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
2015
+ "faculty_name": "Ms. Vinutha M",
2016
+ "subject_code": "OS",
2017
+ "section_id": "3B",
2018
+ "duration": 1
2019
+ },
2020
+ "DSA-3A-0": {
2021
+ "start_slot": 17,
2022
+ "day_index": 2,
2023
+ "day_name": "Wednesday",
2024
+ "period_index": 1,
2025
+ "room_id": "AI09 3F 08",
2026
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
2027
+ "faculty_name": "Ms. Surbhi",
2028
+ "subject_code": "DSA",
2029
+ "section_id": "3A",
2030
+ "duration": 1
2031
+ },
2032
+ "DSA-3A-1": {
2033
+ "start_slot": 3,
2034
+ "day_index": 0,
2035
+ "day_name": "Monday",
2036
+ "period_index": 3,
2037
+ "room_id": "AI09 2F 08",
2038
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2039
+ "faculty_name": "Ms. Surbhi",
2040
+ "subject_code": "DSA",
2041
+ "section_id": "3A",
2042
+ "duration": 1
2043
+ },
2044
+ "DSA-3A-2": {
2045
+ "start_slot": 35,
2046
+ "day_index": 4,
2047
+ "day_name": "Friday",
2048
+ "period_index": 3,
2049
+ "room_id": "AI09 3F 01",
2050
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
2051
+ "faculty_name": "Ms. Surbhi",
2052
+ "subject_code": "DSA",
2053
+ "section_id": "3A",
2054
+ "duration": 1
2055
+ },
2056
+ "DSA-3B-0": {
2057
+ "start_slot": 3,
2058
+ "day_index": 0,
2059
+ "day_name": "Monday",
2060
+ "period_index": 3,
2061
+ "room_id": "AI09 3F 03",
2062
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2063
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2064
+ "subject_code": "DSA",
2065
+ "section_id": "3B",
2066
+ "duration": 1
2067
+ },
2068
+ "DSA-3B-1": {
2069
+ "start_slot": 11,
2070
+ "day_index": 1,
2071
+ "day_name": "Tuesday",
2072
+ "period_index": 3,
2073
+ "room_id": "AI09 2F 13",
2074
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
2075
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2076
+ "subject_code": "DSA",
2077
+ "section_id": "3B",
2078
+ "duration": 1
2079
+ },
2080
+ "DSA-3B-2": {
2081
+ "start_slot": 17,
2082
+ "day_index": 2,
2083
+ "day_name": "Wednesday",
2084
+ "period_index": 1,
2085
+ "room_id": "AI09 1F26",
2086
+ "room_name": "AI09 1F26 (ECE BLOCK)",
2087
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2088
+ "subject_code": "DSA",
2089
+ "section_id": "3B",
2090
+ "duration": 1
2091
+ },
2092
+ "DSA Lab-3A-B1-BLOCK": {
2093
+ "start_slot": 10,
2094
+ "day_index": 1,
2095
+ "day_name": "Tuesday",
2096
+ "period_index": 2,
2097
+ "room_id": "AI03 2F 02",
2098
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2099
+ "faculty_name": "Surbhi / Nanda Kumar N",
2100
+ "subject_code": "DSA Lab",
2101
+ "section_id": "3A-B1",
2102
+ "duration": 2
2103
+ },
2104
+ "DSA Lab-3A-B2-BLOCK": {
2105
+ "start_slot": 20,
2106
+ "day_index": 2,
2107
+ "day_name": "Wednesday",
2108
+ "period_index": 4,
2109
+ "room_id": "AI03 2F 02",
2110
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2111
+ "faculty_name": "Nanda Kumar N / Surbhi",
2112
+ "subject_code": "DSA Lab",
2113
+ "section_id": "3A-B2",
2114
+ "duration": 2
2115
+ },
2116
+ "DSA Lab-3B-B1-BLOCK": {
2117
+ "start_slot": 24,
2118
+ "day_index": 3,
2119
+ "day_name": "Thursday",
2120
+ "period_index": 0,
2121
+ "room_id": "AI03 2F 02",
2122
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2123
+ "faculty_name": "Surbhi / Mhd Tahir Mirji",
2124
+ "subject_code": "DSA Lab",
2125
+ "section_id": "3B-B1",
2126
+ "duration": 2
2127
+ },
2128
+ "OOP with Java-3A-0": {
2129
+ "start_slot": 27,
2130
+ "day_index": 3,
2131
+ "day_name": "Thursday",
2132
+ "period_index": 3,
2133
+ "room_id": "AI09 3F 03",
2134
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2135
+ "faculty_name": "Nandita Tarikeri",
2136
+ "subject_code": "OOP with Java",
2137
+ "section_id": "3A",
2138
+ "duration": 1
2139
+ },
2140
+ "OOP with Java-3B-0": {
2141
+ "start_slot": 32,
2142
+ "day_index": 4,
2143
+ "day_name": "Friday",
2144
+ "period_index": 0,
2145
+ "room_id": "AI09 1F26",
2146
+ "room_name": "AI09 1F26 (ECE BLOCK)",
2147
+ "faculty_name": "Nandita Tarikeri",
2148
+ "subject_code": "OOP with Java",
2149
+ "section_id": "3B",
2150
+ "duration": 1
2151
+ },
2152
+ "EDA Lab-3A-BLOCK": {
2153
+ "start_slot": 12,
2154
+ "day_index": 1,
2155
+ "day_name": "Tuesday",
2156
+ "period_index": 4,
2157
+ "room_id": "AI09 3F 08",
2158
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
2159
+ "faculty_name": "Dr. Kavitha Nair R",
2160
+ "subject_code": "EDA Lab",
2161
+ "section_id": "3A",
2162
+ "duration": 2
2163
+ },
2164
+ "EDA Lab-3B-BLOCK": {
2165
+ "start_slot": 12,
2166
+ "day_index": 1,
2167
+ "day_name": "Tuesday",
2168
+ "period_index": 4,
2169
+ "room_id": "AI09 2F 12",
2170
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2171
+ "faculty_name": "Dr. Kavitha Nair R",
2172
+ "subject_code": "EDA Lab",
2173
+ "section_id": "3B",
2174
+ "duration": 2
2175
+ },
2176
+ "PM with Git-3A-BLOCK": {
2177
+ "start_slot": 12,
2178
+ "day_index": 1,
2179
+ "day_name": "Tuesday",
2180
+ "period_index": 4,
2181
+ "room_id": "AI09 2F 14",
2182
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
2183
+ "faculty_name": "Mr. Sanjay P",
2184
+ "subject_code": "PM with Git",
2185
+ "section_id": "3A",
2186
+ "duration": 2
2187
+ },
2188
+ "PM with Git-3B-BLOCK": {
2189
+ "start_slot": 12,
2190
+ "day_index": 1,
2191
+ "day_name": "Tuesday",
2192
+ "period_index": 4,
2193
+ "room_id": "AI09 2F 08",
2194
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2195
+ "faculty_name": "Mr. Sanjay P",
2196
+ "subject_code": "PM with Git",
2197
+ "section_id": "3B",
2198
+ "duration": 2
2199
+ },
2200
+ "Maths LE-3A-0": {
2201
+ "start_slot": 36,
2202
+ "day_index": 4,
2203
+ "day_name": "Friday",
2204
+ "period_index": 4,
2205
+ "room_id": "AI09 3F 08",
2206
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
2207
+ "faculty_name": "Maths Department",
2208
+ "subject_code": "Maths LE",
2209
+ "section_id": "3A",
2210
+ "duration": 1
2211
+ },
2212
+ "Maths LE-3B-0": {
2213
+ "start_slot": 21,
2214
+ "day_index": 2,
2215
+ "day_name": "Wednesday",
2216
+ "period_index": 5,
2217
+ "room_id": "AI09 1F26",
2218
+ "room_name": "AI09 1F26 (ECE BLOCK)",
2219
+ "faculty_name": "Maths Department",
2220
+ "subject_code": "Maths LE",
2221
+ "section_id": "3B",
2222
+ "duration": 1
2223
+ },
2224
+ "Maths (Prob & Stats)-3DS-0": {
2225
+ "start_slot": 32,
2226
+ "day_index": 4,
2227
+ "day_name": "Friday",
2228
+ "period_index": 0,
2229
+ "room_id": "AI09 2F 12",
2230
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2231
+ "faculty_name": "Maths Department",
2232
+ "subject_code": "Maths (Prob & Stats)",
2233
+ "section_id": "3DS",
2234
+ "duration": 1
2235
+ },
2236
+ "Maths (Prob & Stats)-3DS-1": {
2237
+ "start_slot": 18,
2238
+ "day_index": 2,
2239
+ "day_name": "Wednesday",
2240
+ "period_index": 2,
2241
+ "room_id": "AI09 3F 01",
2242
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
2243
+ "faculty_name": "Maths Department",
2244
+ "subject_code": "Maths (Prob & Stats)",
2245
+ "section_id": "3DS",
2246
+ "duration": 1
2247
+ },
2248
+ "Maths (Prob & Stats)-3DS-2": {
2249
+ "start_slot": 11,
2250
+ "day_index": 1,
2251
+ "day_name": "Tuesday",
2252
+ "period_index": 3,
2253
+ "room_id": "AI09 2F 12",
2254
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2255
+ "faculty_name": "Maths Department",
2256
+ "subject_code": "Maths (Prob & Stats)",
2257
+ "section_id": "3DS",
2258
+ "duration": 1
2259
+ },
2260
+ "Maths (Prob & Stats)-3DS-3": {
2261
+ "start_slot": 0,
2262
+ "day_index": 0,
2263
+ "day_name": "Monday",
2264
+ "period_index": 0,
2265
+ "room_id": "AI09 3F 01",
2266
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
2267
+ "faculty_name": "Maths Department",
2268
+ "subject_code": "Maths (Prob & Stats)",
2269
+ "section_id": "3DS",
2270
+ "duration": 1
2271
+ },
2272
+ "Maths (Prob & Stats)-3DS-4": {
2273
+ "start_slot": 27,
2274
+ "day_index": 3,
2275
+ "day_name": "Thursday",
2276
+ "period_index": 3,
2277
+ "room_id": "AI09 3F 01",
2278
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
2279
+ "faculty_name": "Maths Department",
2280
+ "subject_code": "Maths (Prob & Stats)",
2281
+ "section_id": "3DS",
2282
+ "duration": 1
2283
+ },
2284
+ "DDCO-3DS-0": {
2285
+ "start_slot": 24,
2286
+ "day_index": 3,
2287
+ "day_name": "Thursday",
2288
+ "period_index": 0,
2289
+ "room_id": "AI09 3F 03",
2290
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2291
+ "faculty_name": "Mr. Chalasani Jayanth",
2292
+ "subject_code": "DDCO",
2293
+ "section_id": "3DS",
2294
+ "duration": 1
2295
+ },
2296
+ "DDCO-3DS-1": {
2297
+ "start_slot": 8,
2298
+ "day_index": 1,
2299
+ "day_name": "Tuesday",
2300
+ "period_index": 0,
2301
+ "room_id": "AI09 2F 08",
2302
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2303
+ "faculty_name": "Mr. Chalasani Jayanth",
2304
+ "subject_code": "DDCO",
2305
+ "section_id": "3DS",
2306
+ "duration": 1
2307
+ },
2308
+ "DDCO-3DS-2": {
2309
+ "start_slot": 17,
2310
+ "day_index": 2,
2311
+ "day_name": "Wednesday",
2312
+ "period_index": 1,
2313
+ "room_id": "AI09 2F 12",
2314
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2315
+ "faculty_name": "Mr. Chalasani Jayanth",
2316
+ "subject_code": "DDCO",
2317
+ "section_id": "3DS",
2318
+ "duration": 1
2319
+ },
2320
+ "DDCO-3DS-3": {
2321
+ "start_slot": 2,
2322
+ "day_index": 0,
2323
+ "day_name": "Monday",
2324
+ "period_index": 2,
2325
+ "room_id": "AI09 2F 14",
2326
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
2327
+ "faculty_name": "Mr. Chalasani Jayanth",
2328
+ "subject_code": "DDCO",
2329
+ "section_id": "3DS",
2330
+ "duration": 1
2331
+ },
2332
+ "DDCO-3DS-4": {
2333
+ "start_slot": 33,
2334
+ "day_index": 4,
2335
+ "day_name": "Friday",
2336
+ "period_index": 1,
2337
+ "room_id": "AI09 2F 08",
2338
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2339
+ "faculty_name": "Mr. Chalasani Jayanth",
2340
+ "subject_code": "DDCO",
2341
+ "section_id": "3DS",
2342
+ "duration": 1
2343
+ },
2344
+ "OS-3DS-0": {
2345
+ "start_slot": 10,
2346
+ "day_index": 1,
2347
+ "day_name": "Tuesday",
2348
+ "period_index": 2,
2349
+ "room_id": "AI09 2F 13",
2350
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
2351
+ "faculty_name": "Mr. Sanjay P",
2352
+ "subject_code": "OS",
2353
+ "section_id": "3DS",
2354
+ "duration": 1
2355
+ },
2356
+ "OS-3DS-1": {
2357
+ "start_slot": 3,
2358
+ "day_index": 0,
2359
+ "day_name": "Monday",
2360
+ "period_index": 3,
2361
+ "room_id": "AI09 1F 25",
2362
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
2363
+ "faculty_name": "Mr. Sanjay P",
2364
+ "subject_code": "OS",
2365
+ "section_id": "3DS",
2366
+ "duration": 1
2367
+ },
2368
+ "OS-3DS-2": {
2369
+ "start_slot": 34,
2370
+ "day_index": 4,
2371
+ "day_name": "Friday",
2372
+ "period_index": 2,
2373
+ "room_id": "AI09 2F 12",
2374
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2375
+ "faculty_name": "Mr. Sanjay P",
2376
+ "subject_code": "OS",
2377
+ "section_id": "3DS",
2378
+ "duration": 1
2379
+ },
2380
+ "DSA-3DS-0": {
2381
+ "start_slot": 16,
2382
+ "day_index": 2,
2383
+ "day_name": "Wednesday",
2384
+ "period_index": 0,
2385
+ "room_id": "AI09 3F 03",
2386
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2387
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2388
+ "subject_code": "DSA",
2389
+ "section_id": "3DS",
2390
+ "duration": 1
2391
+ },
2392
+ "DSA-3DS-1": {
2393
+ "start_slot": 1,
2394
+ "day_index": 0,
2395
+ "day_name": "Monday",
2396
+ "period_index": 1,
2397
+ "room_id": "AI09 3F 03",
2398
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2399
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2400
+ "subject_code": "DSA",
2401
+ "section_id": "3DS",
2402
+ "duration": 1
2403
+ },
2404
+ "DSA-3DS-2": {
2405
+ "start_slot": 35,
2406
+ "day_index": 4,
2407
+ "day_name": "Friday",
2408
+ "period_index": 3,
2409
+ "room_id": "AI09 3F 08",
2410
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
2411
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2412
+ "subject_code": "DSA",
2413
+ "section_id": "3DS",
2414
+ "duration": 1
2415
+ },
2416
+ "DSA Lab-3DS-B1-BLOCK": {
2417
+ "start_slot": 6,
2418
+ "day_index": 0,
2419
+ "day_name": "Monday",
2420
+ "period_index": 6,
2421
+ "room_id": "AI03 2F 01",
2422
+ "room_name": "AI03 2F 01 (ASD BLOCK)",
2423
+ "faculty_name": "Nanda Kumar N / Sanjay P",
2424
+ "subject_code": "DSA Lab",
2425
+ "section_id": "3DS-B1",
2426
+ "duration": 2
2427
+ },
2428
+ "OOP with Java-3DS-0": {
2429
+ "start_slot": 25,
2430
+ "day_index": 3,
2431
+ "day_name": "Thursday",
2432
+ "period_index": 1,
2433
+ "room_id": "AI09 2F 08",
2434
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2435
+ "faculty_name": "Nandita Tarikeri",
2436
+ "subject_code": "OOP with Java",
2437
+ "section_id": "3DS",
2438
+ "duration": 1
2439
+ },
2440
+ "EDA Lab-3DS-BLOCK": {
2441
+ "start_slot": 12,
2442
+ "day_index": 1,
2443
+ "day_name": "Tuesday",
2444
+ "period_index": 4,
2445
+ "room_id": "AI09 3F 01",
2446
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
2447
+ "faculty_name": "Dr. Kavitha Nair R",
2448
+ "subject_code": "EDA Lab",
2449
+ "section_id": "3DS",
2450
+ "duration": 2
2451
+ },
2452
+ "Maths LE-3DS-0": {
2453
+ "start_slot": 31,
2454
+ "day_index": 3,
2455
+ "day_name": "Thursday",
2456
+ "period_index": 7,
2457
+ "room_id": "AI09 2F 08",
2458
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2459
+ "faculty_name": "Maths Department",
2460
+ "subject_code": "Maths LE",
2461
+ "section_id": "3DS",
2462
+ "duration": 1
2463
+ },
2464
+ "DV Lab-5A-B1-BLOCK": {
2465
+ "start_slot": 38,
2466
+ "day_index": 4,
2467
+ "day_name": "Friday",
2468
+ "period_index": 6,
2469
+ "room_id": "AI03 2F 02",
2470
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2471
+ "faculty_name": "S Anu Pallavi / Jovin Deglus",
2472
+ "subject_code": "DV Lab",
2473
+ "section_id": "5A-B1",
2474
+ "duration": 2
2475
+ },
2476
+ "DV Lab-5A-B2-BLOCK": {
2477
+ "start_slot": 29,
2478
+ "day_index": 3,
2479
+ "day_name": "Thursday",
2480
+ "period_index": 5,
2481
+ "room_id": "AI03 2F 01",
2482
+ "room_name": "AI03 2F 01 (ASD BLOCK)",
2483
+ "faculty_name": "Abhijith S / Chalasani Jayanth",
2484
+ "subject_code": "DV Lab",
2485
+ "section_id": "5A-B2",
2486
+ "duration": 2
2487
+ },
2488
+ "DSA Lab-3B-B2-BLOCK": {
2489
+ "start_slot": 29,
2490
+ "day_index": 3,
2491
+ "day_name": "Thursday",
2492
+ "period_index": 5,
2493
+ "room_id": "AI03 2F 02",
2494
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2495
+ "faculty_name": "Mhd Tahir Mirji / Surbhi",
2496
+ "subject_code": "DSA Lab",
2497
+ "section_id": "3B-B2",
2498
+ "duration": 2
2499
+ },
2500
+ "DSA Lab-3DS-B2-BLOCK": {
2501
+ "start_slot": 14,
2502
+ "day_index": 1,
2503
+ "day_name": "Tuesday",
2504
+ "period_index": 6,
2505
+ "room_id": "AI03 2F 02",
2506
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2507
+ "faculty_name": "Mhd Tahir Mirji / Nanda Kumar N",
2508
+ "subject_code": "DSA Lab",
2509
+ "section_id": "3DS-B2",
2510
+ "duration": 2
2511
+ },
2512
+ "PM with Git-3DS-BLOCK": {
2513
+ "start_slot": 12,
2514
+ "day_index": 1,
2515
+ "day_name": "Tuesday",
2516
+ "period_index": 4,
2517
+ "room_id": "AI09 1F26",
2518
+ "room_name": "AI09 1F26 (ECE BLOCK)",
2519
+ "faculty_name": "Mr. Sanjay P",
2520
+ "subject_code": "PM with Git",
2521
+ "section_id": "3DS",
2522
+ "duration": 2
2523
+ },
2524
+ "EDA Lab 2-3A-BLOCK": {
2525
+ "start_slot": 38,
2526
+ "day_index": 4,
2527
+ "day_name": "Friday",
2528
+ "period_index": 6,
2529
+ "room_id": "AI09 2F 14",
2530
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
2531
+ "faculty_name": "Dr. Kavitha Nair R",
2532
+ "subject_code": "EDA Lab 2",
2533
+ "section_id": "3A",
2534
+ "duration": 2
2535
+ },
2536
+ "EDA Lab 2-3B-BLOCK": {
2537
+ "start_slot": 38,
2538
+ "day_index": 4,
2539
+ "day_name": "Friday",
2540
+ "period_index": 6,
2541
+ "room_id": "AI09 2F 13",
2542
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
2543
+ "faculty_name": "Dr. Kavitha Nair R",
2544
+ "subject_code": "EDA Lab 2",
2545
+ "section_id": "3B",
2546
+ "duration": 2
2547
+ },
2548
+ "EDA Lab 2-3DS-BLOCK": {
2549
+ "start_slot": 38,
2550
+ "day_index": 4,
2551
+ "day_name": "Friday",
2552
+ "period_index": 6,
2553
+ "room_id": "AI09 3F 08",
2554
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
2555
+ "faculty_name": "Dr. Kavitha Nair R",
2556
+ "subject_code": "EDA Lab 2",
2557
+ "section_id": "3DS",
2558
+ "duration": 2
2559
+ },
2560
+ "PM with Git 2-3A-BLOCK": {
2561
+ "start_slot": 38,
2562
+ "day_index": 4,
2563
+ "day_name": "Friday",
2564
+ "period_index": 6,
2565
+ "room_id": "AI09 2F 08",
2566
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2567
+ "faculty_name": "Mr. Sanjay P",
2568
+ "subject_code": "PM with Git 2",
2569
+ "section_id": "3A",
2570
+ "duration": 2
2571
+ },
2572
+ "PM with Git 2-3B-BLOCK": {
2573
+ "start_slot": 38,
2574
+ "day_index": 4,
2575
+ "day_name": "Friday",
2576
+ "period_index": 6,
2577
+ "room_id": "AI09 3F 03",
2578
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2579
+ "faculty_name": "Mr. Sanjay P",
2580
+ "subject_code": "PM with Git 2",
2581
+ "section_id": "3B",
2582
+ "duration": 2
2583
+ },
2584
+ "PM with Git 2-3DS-BLOCK": {
2585
+ "start_slot": 38,
2586
+ "day_index": 4,
2587
+ "day_name": "Friday",
2588
+ "period_index": 6,
2589
+ "room_id": "AI09 2F 12",
2590
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2591
+ "faculty_name": "Mr. Sanjay P",
2592
+ "subject_code": "PM with Git 2",
2593
+ "section_id": "3DS",
2594
+ "duration": 2
2595
+ }
2596
+ }
2597
+ }
partial_optimizer.py ADDED
@@ -0,0 +1,1126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # partial_optimizer.py
2
+ # Comprehensive timetable operations engine.
3
+ # Handles ALL types of changes — not just rescheduling.
4
+
5
+ from typing import List, Dict, Any, Tuple, Optional
6
+ from collections import defaultdict
7
+ from ortools.sat.python import cp_model
8
+ from copy import deepcopy
9
+
10
+ from models import Task, Faculty, Section, Room, SubjectType
11
+ import constants as const
12
+
13
+ DAY_INDEX = {'MON':0,'TUE':1,'WED':2,'THU':3,'FRI':4,'SAT':5}
14
+ DAY_NAME = {v: k for k, v in DAY_INDEX.items()}
15
+
16
+ def _slot(day, period): return day * const.NUM_TEACHING_SLOTS_PER_DAY + period
17
+ def _day(slot): return slot // const.NUM_TEACHING_SLOTS_PER_DAY
18
+ def _period(slot): return slot % const.NUM_TEACHING_SLOTS_PER_DAY
19
+
20
+ MORNING_PERIODS = list(range(0, const.NUM_TEACHING_SLOTS_PER_DAY // 2))
21
+ AFTERNOON_PERIODS = list(range(const.NUM_TEACHING_SLOTS_PER_DAY // 2,
22
+ const.NUM_TEACHING_SLOTS_PER_DAY))
23
+
24
+
25
+ class PartialOptimizer:
26
+ """
27
+ Handles every possible timetable operation via natural language → JSON constraint.
28
+
29
+ Supported operations:
30
+ ┌─────────────────────────────────────────────────────────────────┐
31
+ │ SCHEDULING │
32
+ │ FACULTY_UNAVAILABLE — block faculty on day/slot │
33
+ │ FACULTY_FREE_DAY — give faculty a free day │
34
+ │ FACULTY_MAX_DAILY_HOURS — limit daily hours │
35
+ │ SECTION_FREE_SLOT — block a slot for a section │
36
+ │ WORKING_DAYS — restrict to certain days │
37
+ │ SUBJECT_PREFERRED_TIME — move subject to morning/afternoon │
38
+ │ HEAVY_SUBJECT_MORNING — heavy subjects in morning │
39
+ │ LAB_MUST_CONSECUTIVE — labs in consecutive slots │
40
+ │ │
41
+ │ DIRECT CHANGES (no re-solve needed) │
42
+ │ FACULTY_SUBSTITUTION — replace faculty for a day/subject │
43
+ │ MOVE_CLASS — move specific class to new slot │
44
+ │ SWAP_CLASSES — swap two classes with each other │
45
+ │ CANCEL_CLASS — remove a class from timetable │
46
+ │ CHANGE_ROOM — assign different room to a class │
47
+ │ ADD_EXTRA_CLASS — add a makeup/extra class │
48
+ │ MARK_HOLIDAY — clear all classes on a day │
49
+ │ RESCHEDULE_LAB — move lab to a new day │
50
+ │ CHANGE_FACULTY — permanently change who teaches │
51
+ │ FREEZE_SLOT — lock a slot so it never changes │
52
+ └─────────────────────────────────────────────────────────────────┘
53
+ """
54
+
55
+ def __init__(self, tasks, faculties, sections, rooms, current_solution):
56
+ self.tasks = tasks
57
+ self.faculties = faculties
58
+ self.sections = sections
59
+ self.rooms = rooms
60
+ self.current_solution = current_solution
61
+
62
+ self.tasks_by_id = {t.task_id: t for t in tasks}
63
+ self.fac_by_id = {f.id: f for f in faculties}
64
+ self.room_by_id = {r.room_id: r for r in rooms}
65
+ self.tasks_by_faculty = defaultdict(list)
66
+ self.tasks_by_section = defaultdict(list)
67
+ self.tasks_by_subject = defaultdict(list)
68
+ for t in tasks:
69
+ self.tasks_by_faculty[t.faculty.id].append(t)
70
+ self.tasks_by_section[t.section.section_id].append(t)
71
+ self.tasks_by_subject[t.subject.subject_code].append(t)
72
+
73
+ # ═════════════════════════════════════════════════════════════════
74
+ # PUBLIC ENTRY POINT
75
+ # ═════════════════════════════════════════════════════════════════
76
+
77
+ def apply_constraint_and_reoptimize(
78
+ self,
79
+ slm_constraint: Dict[str, Any],
80
+ time_limit: int = 30
81
+ ) -> Tuple[str, Dict[str, Any], List[str], str]:
82
+ """
83
+ Route to the correct handler based on constraint type.
84
+ Returns: (status, new_solution, affected_task_ids, summary)
85
+ """
86
+ ctype = slm_constraint.get('type', '').upper()
87
+
88
+ # Direct mutation handlers (no CP-SAT needed)
89
+ direct_handlers = {
90
+ 'FACULTY_SUBSTITUTION': self._op_faculty_substitution,
91
+ 'MOVE_CLASS': self._op_move_class,
92
+ 'SWAP_CLASSES': self._op_swap_classes,
93
+ 'CANCEL_CLASS': self._op_cancel_class,
94
+ 'CHANGE_ROOM': self._op_change_room,
95
+ 'ADD_EXTRA_CLASS': self._op_add_extra_class,
96
+ 'MARK_HOLIDAY': self._op_mark_holiday,
97
+ 'RESCHEDULE_LAB': self._op_reschedule_lab,
98
+ 'CHANGE_FACULTY': self._op_change_faculty,
99
+ 'FREEZE_SLOT': self._op_freeze_slot,
100
+ }
101
+
102
+ # Re-optimization handlers (CP-SAT partial solve)
103
+ reopt_handlers = {
104
+ 'FACULTY_UNAVAILABLE': True,
105
+ 'FACULTY_FREE_DAY': True,
106
+ 'FACULTY_MAX_DAILY_HOURS': True,
107
+ 'FACULTY_NO_CONSECUTIVE': True,
108
+ 'SECTION_FREE_SLOT': True,
109
+ 'WORKING_DAYS': True,
110
+ 'SUBJECT_PREFERRED_TIME': True,
111
+ 'HEAVY_SUBJECT_MORNING': True,
112
+ 'LAB_MUST_CONSECUTIVE': True,
113
+ 'NO_BACK_TO_BACK_SUBJECTS': True,
114
+ 'DISTRIBUTE_SUBJECTS_EVENLY': True,
115
+ 'SUBJECT_SPACING': True,
116
+ 'NO_FREE_PERIOD': True,
117
+ }
118
+
119
+ # ── Type aliases — map SLM variants to canonical types ──────────
120
+ aliases = {
121
+ 'SUBJECT_FREE_DAY': 'CANCEL_CLASS',
122
+ 'FACULTY_LEAVE': 'FACULTY_SUBSTITUTION',
123
+ 'BLOCK_SLOT': 'SECTION_FREE_SLOT',
124
+ 'REMOVE_CLASS': 'CANCEL_CLASS',
125
+ 'DELETE_CLASS': 'CANCEL_CLASS',
126
+ 'CLASS_CANCELLED': 'CANCEL_CLASS',
127
+ 'SHIFT_CLASS': 'MOVE_CLASS',
128
+ 'RELOCATE_CLASS': 'MOVE_CLASS',
129
+ 'TEACHER_SUBSTITUTION': 'FACULTY_SUBSTITUTION',
130
+ 'REPLACE_FACULTY': 'FACULTY_SUBSTITUTION',
131
+ 'SWAP_FACULTY': 'FACULTY_SUBSTITUTION',
132
+ 'SUBJECT_UNAVAILABLE': 'CANCEL_CLASS',
133
+ 'NO_CLASS': 'CANCEL_CLASS',
134
+ 'HOLIDAY': 'MARK_HOLIDAY',
135
+ 'MOVE_LAB': 'RESCHEDULE_LAB',
136
+ 'SHIFT_LAB': 'RESCHEDULE_LAB',
137
+ 'LOCK_SLOT': 'FREEZE_SLOT',
138
+ 'PIN_SLOT': 'FREEZE_SLOT',
139
+ }
140
+ if ctype in aliases:
141
+ slm_constraint = dict(slm_constraint)
142
+ slm_constraint['type'] = aliases[ctype]
143
+ ctype = aliases[ctype]
144
+
145
+ if ctype in direct_handlers:
146
+ return direct_handlers[ctype](slm_constraint)
147
+ elif ctype in reopt_handlers:
148
+ return self._reoptimize(slm_constraint, time_limit)
149
+ else:
150
+ return ('NO_CHANGE', self.current_solution, [],
151
+ f'⚠️ Unknown constraint type: {ctype}')
152
+
153
+ # ═════════════════════════════════════════════════════════════════
154
+ # DIRECT MUTATION OPERATIONS
155
+ # ═════════════════════════════════════════════════════════════════
156
+
157
+ def _op_faculty_substitution(self, c):
158
+ """
159
+ Replace from_faculty with to_faculty.
160
+ Optionally filter by days and/or subjects.
161
+ Prompt examples:
162
+ "Replace Prof. Anu's classes on Friday with Prof. Syed"
163
+ "Prof. Kavitha is on leave Monday, Praveen will cover"
164
+ "Syed will take all of Anu's NLP classes"
165
+ """
166
+ from_id = c.get('from_faculty_id') or c.get('from_faculty')
167
+ to_id = c.get('to_faculty_id') or c.get('to_faculty')
168
+ days = [DAY_INDEX[d] for d in (c.get('days') or []) if d in DAY_INDEX]
169
+ scodes = set(c.get('subject_codes') or [])
170
+ sections = set(c.get('section_ids') or [])
171
+
172
+ if not from_id or not to_id:
173
+ return self._fail('FACULTY_SUBSTITUTION needs from_faculty_id and to_faculty_id')
174
+
175
+ to_fac = self.fac_by_id.get(to_id)
176
+ if not to_fac:
177
+ return self._fail(f'Faculty not found: {to_id}')
178
+
179
+ new_sol, changes = dict(self.current_solution), []
180
+ for task_id, info in self.current_solution.items():
181
+ task = self.tasks_by_id.get(task_id)
182
+ if not task or task.faculty.id != from_id: continue
183
+ if days and info['day_index'] not in days: continue
184
+ if scodes and task.subject.subject_code not in scodes: continue
185
+ if sections and not any(self._sec_matches(task.section.section_id, s) for s in sections): continue
186
+
187
+ updated = dict(info)
188
+ updated['faculty_name'] = to_fac.name
189
+ new_sol[task_id] = updated
190
+ changes.append(
191
+ f"• {info['subject_code'].upper()} ({info['section_id']}) "
192
+ f"{const.DAYS[info['day_index']]} P{info['period_index']+1}: "
193
+ f"{task.faculty.name} → {to_fac.name}"
194
+ )
195
+
196
+ if not changes:
197
+ return ('NO_CHANGE', self.current_solution, [],
198
+ f'No matching classes found for substitution.')
199
+ return ('FEASIBLE', new_sol, [],
200
+ f"✅ {len(changes)} class(es) reassigned:\n" + "\n".join(changes))
201
+
202
+ def _op_move_class(self, c):
203
+ """
204
+ Move a specific class to a new day/period.
205
+ Prompt examples:
206
+ "Move Monday's NLP class of 6A to Wednesday period 3"
207
+ "Shift Anu's Tuesday class to Thursday"
208
+ "Move section 5A's TOC from Monday to Friday"
209
+ """
210
+ from_day = DAY_INDEX.get((c.get('from_day') or '').upper())
211
+ to_day = DAY_INDEX.get((c.get('to_day') or '').upper())
212
+ from_period = c.get('from_period') # 1-based
213
+ to_period = c.get('to_period') # 1-based
214
+ scode = c.get('subject_code')
215
+ sid = c.get('section_id')
216
+ fid = c.get('faculty_id')
217
+
218
+ if from_day is None or to_day is None:
219
+ return self._fail('MOVE_CLASS needs from_day and to_day')
220
+
221
+ new_sol, changes = dict(self.current_solution), []
222
+ for task_id, info in self.current_solution.items():
223
+ task = self.tasks_by_id.get(task_id)
224
+ if not task: continue
225
+ if info['day_index'] != from_day: continue
226
+ if scode and task.subject.subject_code != scode: continue
227
+ if sid and not self._sec_matches(task.section.section_id, sid): continue
228
+ if fid and task.faculty.id != fid: continue
229
+ if from_period and info['period_index'] != from_period - 1: continue
230
+
231
+ # Build candidate periods to try: specified first, then all others
232
+ preferred = (to_period - 1) if to_period else info['period_index']
233
+ candidates = [preferred] + [
234
+ p for p in range(const.NUM_TEACHING_SLOTS_PER_DAY)
235
+ if p != preferred
236
+ ]
237
+
238
+ placed = False
239
+ fac_id = task.faculty.id if task else None
240
+ sec_id = task.section.section_id if task else None
241
+ for try_period in candidates:
242
+ if try_period + task.duration > const.NUM_TEACHING_SLOTS_PER_DAY:
243
+ continue
244
+ # Check faculty conflict
245
+ fac_conflict = self._check_conflict(
246
+ task_id, to_day, try_period, task.duration, fid=fac_id)
247
+ # Check section conflict
248
+ sec_conflict = self._check_conflict(
249
+ task_id, to_day, try_period, task.duration, sid=sec_id)
250
+ if fac_conflict or sec_conflict:
251
+ continue
252
+ updated = dict(info)
253
+ updated['day_index'] = to_day
254
+ updated['day_name'] = const.DAYS[to_day]
255
+ updated['period_index'] = try_period
256
+ updated['start_slot'] = _slot(to_day, try_period)
257
+ new_sol[task_id] = updated
258
+ changes.append(
259
+ f"• {info['subject_code'].upper()} ({info['section_id']}): "
260
+ f"{const.DAYS[from_day]} P{info['period_index']+1} → "
261
+ f"{const.DAYS[to_day]} P{try_period+1}"
262
+ + (f" (requested P{to_period}, auto-placed)" if to_period and try_period != to_period-1 else "")
263
+ )
264
+ placed = True
265
+ break
266
+
267
+ if not placed:
268
+ changes.append(
269
+ f"⚠️ No free slot on {const.DAYS[to_day]} for "
270
+ f"{info['subject_code'].upper()} ({info['section_id']}) "
271
+ f"— all periods occupied"
272
+ )
273
+
274
+ if not changes:
275
+ return ('NO_CHANGE', self.current_solution, [], 'No matching class found to move.')
276
+ return ('FEASIBLE', new_sol, [],
277
+ f"✅ Move operation:\n" + "\n".join(changes))
278
+
279
+ def _op_swap_classes(self, c):
280
+ """
281
+ Swap two classes with each other.
282
+ Prompt examples:
283
+ "Swap Monday P1 NLP with Wednesday P3 ML for section 6A"
284
+ "Swap Anu's Monday class with Syed's Wednesday class"
285
+ """
286
+ task1_id = c.get('task1_id')
287
+ task2_id = c.get('task2_id')
288
+ day1 = DAY_INDEX.get((c.get('day1') or '').upper())
289
+ day2 = DAY_INDEX.get((c.get('day2') or '').upper())
290
+ period1 = c.get('period1')
291
+ period2 = c.get('period2')
292
+ sid = c.get('section_id')
293
+
294
+ # Extract subject codes for subject-based lookup
295
+ scode1 = c.get('subject_code1') or c.get('subject_code')
296
+ scode2 = c.get('subject_code2')
297
+ scodes = c.get('subject_codes') or []
298
+ if scodes and len(scodes) >= 2:
299
+ scode1, scode2 = scodes[0], scodes[1]
300
+ elif scodes and len(scodes) == 1 and not scode1:
301
+ scode1 = scodes[0]
302
+
303
+ fid1 = c.get('faculty_id1')
304
+ fid2 = c.get('faculty_id2')
305
+
306
+ # Find tasks: try period → subject → faculty (in priority order)
307
+ if not task1_id and day1 is not None:
308
+ if period1 is not None:
309
+ task1_id = self._find_task_at(day1, period1-1, sid)
310
+ if not task1_id and scode1:
311
+ task1_id = self._find_task_by_subject(day1, scode1, sid)
312
+ if not task1_id and fid1:
313
+ task1_id = self._find_task_by_faculty(day1, fid1, sid)
314
+ if not task2_id and day2 is not None:
315
+ if period2 is not None:
316
+ task2_id = self._find_task_at(day2, period2-1, sid)
317
+ if not task2_id and scode2:
318
+ task2_id = self._find_task_by_subject(day2, scode2, sid)
319
+ if not task2_id and fid2:
320
+ task2_id = self._find_task_by_faculty(day2, fid2, sid)
321
+
322
+ if not task1_id or not task2_id:
323
+ d1 = const.DAYS[day1] if day1 is not None else '?'
324
+ d2 = const.DAYS[day2] if day2 is not None else '?'
325
+ return self._fail(
326
+ f'SWAP_CLASSES: could not find both classes. '
327
+ f'Looking for {scode1 or "?"} on {d1} and {scode2 or "?"} on {d2} '
328
+ f'for section {sid or "all"}')
329
+
330
+ info1 = dict(self.current_solution.get(task1_id, {}))
331
+ info2 = dict(self.current_solution.get(task2_id, {}))
332
+
333
+ if not info1 or not info2:
334
+ return self._fail('One or both tasks not found in current schedule.')
335
+
336
+ new_sol = dict(self.current_solution)
337
+
338
+ # Swap day + period + start_slot
339
+ for key in ('day_index','day_name','period_index','start_slot'):
340
+ info1[key], info2[key] = info2[key], info1[key]
341
+
342
+ new_sol[task1_id] = info1
343
+ new_sol[task2_id] = info2
344
+
345
+ t1 = self.tasks_by_id.get(task1_id)
346
+ t2 = self.tasks_by_id.get(task2_id)
347
+ summary = (
348
+ f"✅ Swapped:\n"
349
+ f"• {t1.subject.subject_code.upper() if t1 else task1_id} ↔ "
350
+ f"{t2.subject.subject_code.upper() if t2 else task2_id}"
351
+ )
352
+ return ('FEASIBLE', new_sol, [task1_id, task2_id], summary)
353
+
354
+ def _op_cancel_class(self, c):
355
+ """
356
+ Remove a class from the timetable (e.g. cancelled due to event).
357
+ Prompt examples:
358
+ "Cancel NLP class on Friday for section 6A"
359
+ "No class for 5A on Monday period 2"
360
+ "Cancel all classes on Thursday for Anu"
361
+ """
362
+ day = DAY_INDEX.get((c.get('day') or '').upper())
363
+ period = c.get('period')
364
+ scode = c.get('subject_code')
365
+ sid = c.get('section_id')
366
+ fid = c.get('faculty_id')
367
+
368
+ new_sol, changes = dict(self.current_solution), []
369
+ # sid may be a full section id ("6a-E1") or a parent prefix ("6A")
370
+ sid_is_prefix = sid and '-' not in sid
371
+
372
+ for task_id, info in list(self.current_solution.items()):
373
+ task = self.tasks_by_id.get(task_id)
374
+ if not task: continue
375
+ if day is not None and info['day_index'] != day: continue
376
+ if period is not None and info['period_index'] != period - 1: continue
377
+ if scode and task.subject.subject_code != scode: continue
378
+ if fid and task.faculty.id != fid: continue
379
+ # Section filter: exact match OR parent prefix match
380
+ if sid:
381
+ sec = task.section.section_id
382
+ if sid_is_prefix:
383
+ # "6A" matches "6a-E1", "6a-E2" but NOT "6b-E1"
384
+ if not sec.lower().startswith(sid.lower()):
385
+ continue
386
+ else:
387
+ if sec != sid:
388
+ continue
389
+
390
+ del new_sol[task_id]
391
+ changes.append(
392
+ f"• {info['subject_code'].upper()} ({info['section_id']}) "
393
+ f"{const.DAYS[info['day_index']]} P{info['period_index']+1} — CANCELLED"
394
+ )
395
+
396
+ if not changes:
397
+ return ('NO_CHANGE', self.current_solution, [], 'No matching class found to cancel.')
398
+ return ('FEASIBLE', new_sol, [],
399
+ f"✅ {len(changes)} class(es) cancelled:\n" + "\n".join(changes))
400
+
401
+ def _op_change_room(self, c):
402
+ """
403
+ Assign a different room to a class.
404
+ Prompt examples:
405
+ "Move NLP class on Monday to Lab 2"
406
+ "Change room for 6A's ML on Tuesday to R3"
407
+ "Shift all of section 5A's labs to L2"
408
+ """
409
+ new_room_id = c.get('room_id') or c.get('new_room_id')
410
+ scode = c.get('subject_code')
411
+ sid = c.get('section_id')
412
+ day = DAY_INDEX.get((c.get('day') or '').upper())
413
+ period = c.get('period')
414
+
415
+ if not new_room_id:
416
+ return self._fail('CHANGE_ROOM needs room_id')
417
+
418
+ new_room = self.room_by_id.get(new_room_id)
419
+ if not new_room:
420
+ return self._fail(f'Room not found: {new_room_id}')
421
+
422
+ new_sol, changes = dict(self.current_solution), []
423
+ for task_id, info in self.current_solution.items():
424
+ task = self.tasks_by_id.get(task_id)
425
+ if not task: continue
426
+ if scode and task.subject.subject_code != scode: continue
427
+ if sid and not self._sec_matches(task.section.section_id, sid): continue
428
+ if day is not None and info['day_index'] != day: continue
429
+ if period is not None and info['period_index'] != period-1: continue
430
+
431
+ updated = dict(info)
432
+ updated['room_id'] = new_room.room_id
433
+ updated['room_name'] = f"{new_room.room_id} ({new_room.building})"
434
+ new_sol[task_id] = updated
435
+ changes.append(
436
+ f"• {info['subject_code'].upper()} ({info['section_id']}) "
437
+ f"{const.DAYS[info['day_index']]} P{info['period_index']+1}: "
438
+ f"Room → {new_room.room_id}"
439
+ )
440
+
441
+ if not changes:
442
+ return ('NO_CHANGE', self.current_solution, [], 'No matching class found.')
443
+ return ('FEASIBLE', new_sol, [],
444
+ f"✅ {len(changes)} room change(s):\n" + "\n".join(changes))
445
+
446
+ def _op_add_extra_class(self, c):
447
+ """
448
+ Add a makeup/extra class to the timetable.
449
+ Prompt examples:
450
+ "Add a makeup NLP class on Saturday period 1 for 6A by Anu"
451
+ "Schedule extra ML session for 5B on Friday afternoon"
452
+ """
453
+ scode = c.get('subject_code')
454
+ sid = c.get('section_id')
455
+ fid = c.get('faculty_id')
456
+ day_raw = (c.get('day') or '').upper()
457
+ # day may be a string key like "SAT" or already an int index
458
+ if isinstance(day_raw, int):
459
+ day = day_raw
460
+ else:
461
+ day = DAY_INDEX.get(day_raw)
462
+ period = c.get('period')
463
+ room_id = c.get('room_id')
464
+
465
+ if day is None or not scode or not sid or not fid:
466
+ return self._fail(
467
+ 'ADD_EXTRA_CLASS needs subject_code, section_id, faculty_id, and day')
468
+
469
+ fac = self.fac_by_id.get(fid)
470
+ room = self.room_by_id.get(room_id) if room_id else next(
471
+ (r for r in self.rooms if not r.is_lab), self.rooms[0])
472
+
473
+ if not fac:
474
+ return self._fail(f'Faculty not found: {fid}')
475
+
476
+ day_name = const.DAYS[day] if day < len(const.DAYS) else day_raw
477
+
478
+ # Auto-find free period if not specified
479
+ if not period:
480
+ for try_p in range(const.NUM_TEACHING_SLOTS_PER_DAY):
481
+ fc = self._check_conflict(None, day, try_p, 1, fid=fid)
482
+ sc = self._check_conflict(None, day, try_p, 1, sid=sid)
483
+ if not fc and not sc:
484
+ period = try_p + 1 # 1-indexed
485
+ break
486
+ if not period:
487
+ return self._fail(
488
+ f'No free slot on {day_name} for {scode.upper()} ({sid})')
489
+
490
+ # Check conflict at chosen period
491
+ conflict = (self._check_conflict(None, day, period-1, 1, fid=fid) or
492
+ self._check_conflict(None, day, period-1, 1, sid=sid))
493
+ if conflict:
494
+ return self._fail(f'Slot conflict at P{period}: {conflict}')
495
+
496
+ task_id = f"EXTRA-{scode}-{sid}-{day_name}-P{period}"
497
+ new_sol = dict(self.current_solution)
498
+ new_sol[task_id] = {
499
+ 'start_slot': _slot(day, period-1),
500
+ 'day_index': day,
501
+ 'day_name': day_name,
502
+ 'period_index': period - 1,
503
+ 'room_id': room.room_id,
504
+ 'room_name': f"{room.room_id} ({room.building})",
505
+ 'faculty_name': fac.name,
506
+ 'subject_code': scode,
507
+ 'section_id': sid,
508
+ 'duration': 1,
509
+ 'is_extra': True,
510
+ }
511
+ return ('FEASIBLE', new_sol, [task_id],
512
+ f"✅ Extra class added: {scode.upper()} ({sid}) "
513
+ f"{day_name} P{period} by {fac.name}")
514
+
515
+ def _op_mark_holiday(self, c):
516
+ """
517
+ Clear all classes on a specific day (holiday/event).
518
+ Prompt examples:
519
+ "Mark Friday as holiday"
520
+ "No classes on Monday for all sections"
521
+ "Cancel all Thursday classes for section 6A"
522
+ """
523
+ days = [DAY_INDEX[d] for d in (c.get('days') or []) if d in DAY_INDEX]
524
+ sid = c.get('section_id')
525
+
526
+ if not days:
527
+ return self._fail('MARK_HOLIDAY needs at least one day')
528
+
529
+ new_sol, changes = dict(self.current_solution), []
530
+ for task_id, info in list(self.current_solution.items()):
531
+ if info['day_index'] not in days: continue
532
+ if sid and info['section_id'] != sid: continue
533
+ del new_sol[task_id]
534
+ changes.append(
535
+ f"• {info['subject_code'].upper()} ({info['section_id']}) "
536
+ f"P{info['period_index']+1}"
537
+ )
538
+
539
+ day_names = [const.DAYS[d] for d in days]
540
+ return ('FEASIBLE', new_sol, [],
541
+ f"✅ Holiday on {', '.join(day_names)}: "
542
+ f"{len(changes)} class(es) cleared")
543
+
544
+ def _op_reschedule_lab(self, c):
545
+ """
546
+ Move a lab session to a different day (keeps it as 2-hour block).
547
+ Prompt examples:
548
+ "Move NLP Lab from Wednesday to Thursday"
549
+ "Reschedule ML Lab for section 6A to Friday"
550
+ """
551
+ scode = c.get('subject_code')
552
+ sid = c.get('section_id')
553
+ from_day = DAY_INDEX.get((c.get('from_day') or '').upper())
554
+ to_day = DAY_INDEX.get((c.get('to_day') or '').upper())
555
+ to_period = c.get('to_period', 1)
556
+
557
+ if to_day is None:
558
+ return self._fail('RESCHEDULE_LAB needs to_day')
559
+
560
+ new_sol, changes = dict(self.current_solution), []
561
+ for task_id, info in self.current_solution.items():
562
+ task = self.tasks_by_id.get(task_id)
563
+ if not task or task.duration < 2: continue
564
+
565
+ # Match by subject code OR by partial name match (nlp matches nlplab)
566
+ code_match = (scode and (
567
+ task.subject.subject_code == scode or
568
+ task.subject.subject_code.startswith(scode) or
569
+ scode.startswith(task.subject.subject_code)
570
+ ))
571
+ if not code_match: continue
572
+ if sid and not self._sec_matches(task.section.section_id, sid): continue
573
+ # If from_day specified, only move labs on that day
574
+ if from_day is not None and info['day_index'] != from_day: continue
575
+
576
+ # Auto-find a free lab slot on to_day if specified period conflicts
577
+ # Try: specified period first, then all ALLOWED_LAB_START_INDICES
578
+ candidates = []
579
+ if to_period:
580
+ candidates.append(to_period - 1)
581
+ for p in const.ALLOWED_LAB_START_INDICES:
582
+ if p not in candidates:
583
+ candidates.append(p)
584
+
585
+ placed = False
586
+ fac_id2 = task.faculty.id if task else None
587
+ sec_id2 = task.section.section_id if task else None
588
+ for try_period in candidates:
589
+ # Make sure the 2-hour block doesn't cross day boundary
590
+ if try_period + task.duration > const.NUM_TEACHING_SLOTS_PER_DAY:
591
+ continue
592
+ fac_conflict = self._check_conflict(
593
+ task_id, to_day, try_period, task.duration, fid=fac_id2)
594
+ sec_conflict = self._check_conflict(
595
+ task_id, to_day, try_period, task.duration, sid=sec_id2)
596
+ if fac_conflict or sec_conflict:
597
+ continue
598
+ # Free slot found — place it
599
+ updated = dict(info)
600
+ updated['day_index'] = to_day
601
+ updated['day_name'] = const.DAYS[to_day]
602
+ updated['period_index'] = try_period
603
+ updated['start_slot'] = _slot(to_day, try_period)
604
+ new_sol[task_id] = updated
605
+ changes.append(
606
+ f"• {scode.upper()} Lab ({info['section_id']}): "
607
+ f"{const.DAYS[info['day_index']]} P{info['period_index']+1} → "
608
+ f"{const.DAYS[to_day]} P{try_period+1}"
609
+ )
610
+ placed = True
611
+ break
612
+
613
+ if not placed:
614
+ changes.append(
615
+ f"⚠️ No free lab slot found on {const.DAYS[to_day]} "
616
+ f"for {scode.upper()} ({info['section_id']}) — "
617
+ f"all slots are occupied"
618
+ )
619
+
620
+ if not changes:
621
+ return ('NO_CHANGE', self.current_solution, [], f'Lab {scode} not found.')
622
+ return ('FEASIBLE', new_sol, [],
623
+ f"✅ Lab rescheduled:\n" + "\n".join(changes))
624
+
625
+ def _op_change_faculty(self, c):
626
+ """
627
+ Permanently change faculty for a subject/section (all occurrences).
628
+ Prompt examples:
629
+ "Assign all NLP classes of 6A to Prof. Syed instead of Anu"
630
+ "Transfer Anu's 5B classes to Kavitha permanently"
631
+ """
632
+ from_id = c.get('from_faculty_id') or c.get('from_faculty')
633
+ to_id = c.get('to_faculty_id') or c.get('to_faculty')
634
+ scode = c.get('subject_code')
635
+ sid = c.get('section_id')
636
+
637
+ if not to_id:
638
+ return self._fail('CHANGE_FACULTY needs to_faculty_id')
639
+
640
+ to_fac = self.fac_by_id.get(to_id)
641
+ if not to_fac:
642
+ return self._fail(f'Faculty not found: {to_id}')
643
+
644
+ new_sol, changes = dict(self.current_solution), []
645
+ for task_id, info in self.current_solution.items():
646
+ task = self.tasks_by_id.get(task_id)
647
+ if not task: continue
648
+ if from_id and task.faculty.id != from_id: continue
649
+ if scode and task.subject.subject_code != scode: continue
650
+ if sid and not self._sec_matches(task.section.section_id, sid): continue
651
+
652
+ updated = dict(info)
653
+ updated['faculty_name'] = to_fac.name
654
+ new_sol[task_id] = updated
655
+ changes.append(
656
+ f"• {info['subject_code'].upper()} ({info['section_id']}) "
657
+ f"ALL slots: faculty → {to_fac.name}"
658
+ )
659
+
660
+ if not changes:
661
+ return ('NO_CHANGE', self.current_solution, [], 'No matching classes found.')
662
+ # Deduplicate summary
663
+ unique = list(dict.fromkeys(changes))
664
+ return ('FEASIBLE', new_sol, [],
665
+ f"✅ Faculty changed for {len(changes)} slot(s):\n" + "\n".join(unique[:10]))
666
+
667
+ def _op_freeze_slot(self, c):
668
+ """
669
+ Mark a slot as frozen (metadata only — solver will skip it in future updates).
670
+ Prompt examples:
671
+ "Lock Monday P1 NLP for section 6A"
672
+ "Freeze Anu's Wednesday slots"
673
+ """
674
+ # Freeze is stored as metadata in the info dict
675
+ day = DAY_INDEX.get((c.get('day') or '').upper())
676
+ period = c.get('period')
677
+ sid = c.get('section_id')
678
+ scode = c.get('subject_code')
679
+
680
+ new_sol, changes = dict(self.current_solution), []
681
+ for task_id, info in self.current_solution.items():
682
+ task = self.tasks_by_id.get(task_id)
683
+ if not task: continue
684
+ if day is not None and info['day_index'] != day: continue
685
+ if period is not None and info['period_index'] != period-1: continue
686
+ if sid and not self._sec_matches(task.section.section_id, sid): continue
687
+ if scode and task.subject.subject_code != scode: continue
688
+
689
+ updated = dict(info)
690
+ updated['frozen'] = True
691
+ new_sol[task_id] = updated
692
+ changes.append(
693
+ f"• {info['subject_code'].upper()} ({info['section_id']}) "
694
+ f"{const.DAYS[info['day_index']]} P{info['period_index']+1} — FROZEN"
695
+ )
696
+
697
+ if not changes:
698
+ return ('NO_CHANGE', self.current_solution, [], 'No matching slot to freeze.')
699
+ return ('FEASIBLE', new_sol, [],
700
+ f"✅ {len(changes)} slot(s) frozen:\n" + "\n".join(changes))
701
+
702
+ # ═════════════════════════════════════════════════════════════════
703
+ # CP-SAT RE-OPTIMIZATION (for scheduling constraint types)
704
+ # ═════════════════════════════════════════════════════════════════
705
+
706
+ def _reoptimize(self, slm_constraint, time_limit):
707
+ ctype = slm_constraint.get('type', '').upper()
708
+ affected_ids = self._find_affected_tasks(slm_constraint)
709
+
710
+ if not affected_ids:
711
+ return ('NO_CHANGE', self.current_solution, [],
712
+ f'No tasks affected by: {ctype}')
713
+
714
+ status, partial = self._solve_partial(affected_ids, [slm_constraint], time_limit)
715
+
716
+ if status not in ('OPTIMAL', 'FEASIBLE'):
717
+ return (status, self.current_solution, affected_ids,
718
+ f'❌ Could not reschedule {len(affected_ids)} task(s). '
719
+ f'Constraint may be too restrictive.')
720
+
721
+ new_sol = dict(self.current_solution)
722
+ new_sol.update(partial)
723
+
724
+ changes = []
725
+ for tid in affected_ids:
726
+ old = self.current_solution.get(tid, {})
727
+ new = new_sol.get(tid, {})
728
+ if old and new:
729
+ old_slot = f"{const.DAYS[old['day_index']]} P{old['period_index']+1}"
730
+ new_slot = f"{const.DAYS[new['day_index']]} P{new['period_index']+1}"
731
+ if old_slot != new_slot:
732
+ changes.append(
733
+ f"• {new.get('subject_code','?').upper()} "
734
+ f"({new.get('section_id','?')}): "
735
+ f"{old_slot} → {new_slot}"
736
+ )
737
+
738
+ summary = (f"✅ {len(changes)} slot(s) moved:\n" + "\n".join(changes)
739
+ if changes else "✅ Constraint applied — no slot changes needed.")
740
+ return (status, new_sol, affected_ids, summary)
741
+
742
+ def _find_affected_tasks(self, c):
743
+ ctype = c.get('type', '').upper()
744
+ affected = set()
745
+
746
+ if ctype == 'FACULTY_UNAVAILABLE':
747
+ fid = c.get('faculty_id')
748
+ days = [DAY_INDEX[d] for d in (c.get('days') or []) if d in DAY_INDEX]
749
+ slots = c.get('slots') or []
750
+ for task in self.tasks_by_faculty.get(fid, []):
751
+ info = self.current_solution.get(task.task_id, {})
752
+ if not info: continue
753
+ if not days or info['day_index'] in days:
754
+ if not slots or info['period_index'] in slots:
755
+ affected.add(task.task_id)
756
+
757
+ elif ctype == 'FACULTY_FREE_DAY':
758
+ fid = c.get('faculty_id')
759
+ pday = c.get('preferred_day')
760
+ if pday and pday in DAY_INDEX:
761
+ for task in self.tasks_by_faculty.get(fid, []):
762
+ info = self.current_solution.get(task.task_id, {})
763
+ if info and info['day_index'] == DAY_INDEX[pday]:
764
+ affected.add(task.task_id)
765
+
766
+ elif ctype == 'FACULTY_MAX_DAILY_HOURS':
767
+ fid = c.get('faculty_id')
768
+ max_hrs = c.get('max_hours') or 999
769
+ by_day = defaultdict(list)
770
+ for task in self.tasks_by_faculty.get(fid, []):
771
+ info = self.current_solution.get(task.task_id, {})
772
+ if info: by_day[info['day_index']].append(task.task_id)
773
+ for day, tids in by_day.items():
774
+ total = sum(self.tasks_by_id[t].duration for t in tids)
775
+ if total > max_hrs: affected.update(tids)
776
+
777
+ elif ctype in ('SUBJECT_PREFERRED_TIME', 'HEAVY_SUBJECT_MORNING'):
778
+ scode = c.get('subject_code')
779
+ sid = c.get('section_id')
780
+ period = str(c.get('period', '')).upper()
781
+ target = MORNING_PERIODS if 'MORNING' in period else AFTERNOON_PERIODS
782
+ src = self.tasks_by_subject.get(scode, []) if scode else self.tasks
783
+ for task in src:
784
+ if sid and task.section.section_id != sid: continue
785
+ info = self.current_solution.get(task.task_id, {})
786
+ if info and target and info['period_index'] not in target:
787
+ affected.add(task.task_id)
788
+
789
+ elif ctype == 'SECTION_FREE_SLOT':
790
+ sid = c.get('section_id')
791
+ slot = c.get('slot')
792
+ days = [DAY_INDEX[d] for d in (c.get('days') or []) if d in DAY_INDEX]
793
+ for task in self.tasks_by_section.get(sid, []):
794
+ info = self.current_solution.get(task.task_id, {})
795
+ if not info: continue
796
+ if slot is not None and info['period_index'] == slot - 1:
797
+ if not days or info['day_index'] in days:
798
+ affected.add(task.task_id)
799
+
800
+ elif ctype == 'LAB_MUST_CONSECUTIVE':
801
+ scode = c.get('subject_code')
802
+ for task in self.tasks_by_subject.get(scode, []):
803
+ affected.add(task.task_id)
804
+
805
+ elif ctype == 'WORKING_DAYS':
806
+ allowed = {DAY_INDEX[d] for d in (c.get('days') or []) if d in DAY_INDEX}
807
+ if allowed:
808
+ for task in self.tasks:
809
+ info = self.current_solution.get(task.task_id, {})
810
+ if info and info['day_index'] not in allowed:
811
+ affected.add(task.task_id)
812
+
813
+ elif ctype in ('NO_BACK_TO_BACK_SUBJECTS', 'DISTRIBUTE_SUBJECTS_EVENLY',
814
+ 'SUBJECT_SPACING'):
815
+ scode = c.get('subject_code')
816
+ codes = c.get('subject_codes') or ([scode] if scode else [])
817
+ sid = c.get('section_id')
818
+ for code in codes:
819
+ for task in self.tasks_by_subject.get(code, []):
820
+ if sid and task.section.section_id != sid: continue
821
+ affected.add(task.task_id)
822
+
823
+ elif ctype == 'NO_FREE_PERIOD':
824
+ # Find tasks that are NOT in the required period on any day
825
+ # so we can move them there, OR find tasks blocking required period
826
+ # Strategy: collect all tasks that need to shift to fill the required period
827
+ raw_periods = c.get('periods') or []
828
+ # Resolve -1 to last teaching period
829
+ required_periods = [
830
+ const.NUM_TEACHING_SLOTS_PER_DAY - 1 if p == -1 else p
831
+ for p in raw_periods
832
+ ] # e.g. [0] for P1, [7] for last
833
+ sids = [s.section_id for s in self.sections]
834
+ # Find sections that have a free slot in required period on any day
835
+ # We need to reschedule tasks to fill those gaps
836
+ # Collect all tasks — let solver figure out placement
837
+ for task in self.tasks:
838
+ info = self.current_solution.get(task.task_id, {})
839
+ if info and required_periods:
840
+ # Add tasks not currently in the required period
841
+ if info.get('period_index') not in required_periods:
842
+ affected.add(task.task_id)
843
+ # If no periods specified, affect nothing
844
+ if not required_periods:
845
+ affected = set()
846
+
847
+ else:
848
+ affected.update(t.task_id for t in self.tasks)
849
+
850
+ # Never move frozen slots
851
+ frozen = {tid for tid, info in self.current_solution.items()
852
+ if info.get('frozen')}
853
+ return list(affected - frozen)
854
+
855
+ def _solve_partial(self, affected_ids, constraints, time_limit):
856
+ model = cp_model.CpModel()
857
+ solver = cp_model.CpSolver()
858
+
859
+ affected_tasks = [self.tasks_by_id[tid] for tid in affected_ids
860
+ if tid in self.tasks_by_id]
861
+
862
+ locked_fac = defaultdict(set)
863
+ locked_sec = defaultdict(set)
864
+ locked_room = defaultdict(set)
865
+
866
+ for task_id, info in self.current_solution.items():
867
+ if task_id in affected_ids: continue
868
+ task = self.tasks_by_id.get(task_id)
869
+ if not task: continue
870
+ for offset in range(task.duration):
871
+ s = _slot(info['day_index'], info['period_index']) + offset
872
+ locked_fac[task.faculty.id].add(s)
873
+ locked_sec[task.section.section_id].add(s)
874
+
875
+ task_vars = {}
876
+ for task in affected_tasks:
877
+ allowed = self._compute_allowed(task, constraints, locked_fac, locked_sec)
878
+ if not allowed: allowed = self._all_starts(task)
879
+
880
+ sv = model.NewIntVarFromDomain(
881
+ cp_model.Domain.FromValues(sorted(allowed)), f'{task.task_id}_s')
882
+ ev = model.NewIntVar(0, const.TOTAL_TEACHING_SLOTS_PER_WEEK, f'{task.task_id}_e')
883
+ iv = model.NewIntervalVar(sv, task.duration, ev, f'{task.task_id}_i')
884
+
885
+ ri = model.NewIntVarFromDomain(
886
+ cp_model.Domain.FromValues(self._allowed_rooms(task)), f'{task.task_id}_r')
887
+
888
+ task_vars[task.task_id] = (sv, ev, iv, ri)
889
+
890
+ # No-overlap faculty
891
+ ivs_fac = defaultdict(list)
892
+ for t in affected_tasks:
893
+ ivs_fac[t.faculty.id].append(task_vars[t.task_id][2])
894
+ for ivs in ivs_fac.values():
895
+ if len(ivs) > 1: model.AddNoOverlap(ivs)
896
+
897
+ # No-overlap section
898
+ ivs_sec = defaultdict(list)
899
+ for t in affected_tasks:
900
+ ivs_sec[t.section.section_id].append(task_vars[t.task_id][2])
901
+ for ivs in ivs_sec.values():
902
+ if len(ivs) > 1: model.AddNoOverlap(ivs)
903
+
904
+ content_solution_ref = self.current_solution # used in NO_FREE_PERIOD
905
+
906
+ # ── NO_FREE_PERIOD enforcement ─────────────────────────────────────
907
+ # For each section and each working day, ensure at least one task
908
+ # is assigned to the required period(s).
909
+ for c in constraints:
910
+ if c.get('type','').upper() != 'NO_FREE_PERIOD':
911
+ continue
912
+ required_periods = c.get('periods') or []
913
+ filter_sid = c.get('section_id') # None = all sections
914
+ filter_days = c.get('days') or list(range(const.NUM_WORKING_DAYS))
915
+
916
+ for day in filter_days:
917
+ # Group affected tasks by section
918
+ tasks_by_sec = defaultdict(list)
919
+ for task in affected_tasks:
920
+ if filter_sid and task.section.section_id != filter_sid:
921
+ continue
922
+ tasks_by_sec[task.section.section_id].append(task)
923
+
924
+ for sec_id, sec_tasks in tasks_by_sec.items():
925
+ for req_period in required_periods:
926
+ target_slot = _slot(day, req_period)
927
+ # Create boolean: is_in_slot[task] = 1 if task covers target_slot
928
+ in_slot_vars = []
929
+ for task in sec_tasks:
930
+ if task.task_id not in task_vars:
931
+ continue
932
+ sv = task_vars[task.task_id][0]
933
+ b = model.NewBoolVar(
934
+ f'{task.task_id}_covers_d{day}_p{req_period}')
935
+ # task covers target_slot if sv <= target_slot < sv + duration
936
+ model.Add(sv <= target_slot).OnlyEnforceIf(b)
937
+ model.Add(sv + task.duration > target_slot).OnlyEnforceIf(b)
938
+ model.Add(sv > target_slot).OnlyEnforceIf(b.Not())
939
+ in_slot_vars.append(b)
940
+
941
+ # Also check non-affected (locked) tasks for this section+day+period
942
+ locked_covers = False
943
+ for tid, info in content_solution_ref.items():
944
+ if tid in affected_ids: continue
945
+ t = self.tasks_by_id.get(tid)
946
+ if not t or t.section.section_id != sec_id: continue
947
+ if info['day_index'] != day: continue
948
+ t_start = _slot(info['day_index'], info['period_index'])
949
+ if t_start <= target_slot < t_start + t.duration:
950
+ locked_covers = True
951
+ break
952
+
953
+ # Only add constraint if locked tasks don't already cover it
954
+ if not locked_covers and in_slot_vars:
955
+ model.AddAtLeastOne(in_slot_vars)
956
+
957
+ # Minimize changes from original
958
+ penalties = []
959
+ for task in affected_tasks:
960
+ orig = self.current_solution.get(task.task_id, {})
961
+ if orig:
962
+ orig_slot = _slot(orig['day_index'], orig['period_index'])
963
+ sv = task_vars[task.task_id][0]
964
+ changed = model.NewBoolVar(f'{task.task_id}_chg')
965
+ model.Add(sv != orig_slot).OnlyEnforceIf(changed)
966
+ model.Add(sv == orig_slot).OnlyEnforceIf(changed.Not())
967
+ penalties.append(changed)
968
+ if penalties: model.Minimize(sum(penalties))
969
+
970
+ solver.parameters.max_time_in_seconds = time_limit
971
+ solver.parameters.log_search_progress = False
972
+ sv_val = solver.Solve(model)
973
+
974
+ if sv_val not in (cp_model.OPTIMAL, cp_model.FEASIBLE):
975
+ return ('INFEASIBLE', {})
976
+
977
+ result = {}
978
+ for task in affected_tasks:
979
+ sv, _, _, rv = task_vars[task.task_id]
980
+ start = solver.Value(sv)
981
+ room = self.rooms[solver.Value(rv)]
982
+ result[task.task_id] = {
983
+ 'start_slot': start,
984
+ 'day_index': _day(start),
985
+ 'day_name': const.DAYS[_day(start)],
986
+ 'period_index': _period(start),
987
+ 'room_id': room.room_id,
988
+ 'room_name': f'{room.room_id} ({room.building})',
989
+ 'faculty_name': task.faculty.name,
990
+ 'subject_code': task.subject.subject_code,
991
+ 'section_id': task.section.section_id,
992
+ 'duration': task.duration,
993
+ }
994
+
995
+ status = 'OPTIMAL' if sv_val == cp_model.OPTIMAL else 'FEASIBLE'
996
+ return (status, result)
997
+
998
+ # ═════════════════════════════════════════════════════════════════
999
+ # HELPERS
1000
+ # ═════════════════════════════════════════════════════════════════
1001
+
1002
+ def _sec_matches(self, task_section_id: str, sid_filter: str) -> bool:
1003
+ """
1004
+ Returns True if task_section_id matches sid_filter.
1005
+ sid_filter can be:
1006
+ - exact id: "6a-E1" -> only matches "6a-E1"
1007
+ - parent prefix: "6A" -> matches "6a-E1", "6a-E2" but not "6b-E1"
1008
+ - None: matches everything
1009
+ """
1010
+ if not sid_filter:
1011
+ return True
1012
+ if '-' not in sid_filter:
1013
+ # prefix match
1014
+ return task_section_id.lower().startswith(sid_filter.lower())
1015
+ return task_section_id == sid_filter
1016
+
1017
+ def _check_conflict(self, skip_task_id, day, period, duration,
1018
+ fid=None, sid=None):
1019
+ """Returns conflict description if slot is taken, else None."""
1020
+ target_slots = set(range(_slot(day, period),
1021
+ _slot(day, period) + duration))
1022
+ for task_id, info in self.current_solution.items():
1023
+ if task_id == skip_task_id: continue
1024
+ task = self.tasks_by_id.get(task_id)
1025
+ if not task: continue
1026
+ task_slots = set(range(
1027
+ _slot(info['day_index'], info['period_index']),
1028
+ _slot(info['day_index'], info['period_index']) + info['duration']
1029
+ ))
1030
+ if not target_slots.isdisjoint(task_slots):
1031
+ if fid and task.faculty.id == fid:
1032
+ return f"{task.faculty.name} busy"
1033
+ if sid and task.section.section_id == sid:
1034
+ return f"Section {info['section_id']} busy"
1035
+ if not fid and not sid:
1036
+ return f"{info['subject_code']} at that slot"
1037
+ return None
1038
+
1039
+ def _find_task_at(self, day, period, sid=None):
1040
+ for task_id, info in self.current_solution.items():
1041
+ if info['day_index'] != day: continue
1042
+ if info['period_index'] != period: continue
1043
+ if sid and not self._sec_matches(info['section_id'], sid): continue
1044
+ return task_id
1045
+ return None
1046
+
1047
+ def _find_task_by_subject(self, day, scode, sid=None):
1048
+ """Find first task matching subject code on a given day."""
1049
+ for task_id, info in self.current_solution.items():
1050
+ task = self.tasks_by_id.get(task_id)
1051
+ if not task: continue
1052
+ if info['day_index'] != day: continue
1053
+ tc = task.subject.subject_code.lower()
1054
+ sc = scode.lower()
1055
+ if tc != sc and not tc.startswith(sc) and not sc.startswith(tc):
1056
+ continue
1057
+ if sid and not self._sec_matches(info['section_id'], sid): continue
1058
+ return task_id
1059
+ return None
1060
+
1061
+ def _find_task_by_faculty(self, day, fid, sid=None):
1062
+ """Find first task by faculty on a given day."""
1063
+ for task_id, info in self.current_solution.items():
1064
+ task = self.tasks_by_id.get(task_id)
1065
+ if not task: continue
1066
+ if info['day_index'] != day: continue
1067
+ if task.faculty.id != fid: continue
1068
+ if sid and not self._sec_matches(info['section_id'], sid): continue
1069
+ return task_id
1070
+ return None
1071
+
1072
+ def _all_starts(self, task):
1073
+ if task.duration > 1:
1074
+ result = []
1075
+ for day in range(const.NUM_WORKING_DAYS):
1076
+ for pos in const.ALLOWED_LAB_START_INDICES:
1077
+ result.append(day * const.NUM_TEACHING_SLOTS_PER_DAY + pos)
1078
+ return result
1079
+ return list(range(const.TOTAL_TEACHING_SLOTS_PER_WEEK))
1080
+
1081
+ def _compute_allowed(self, task, constraints, locked_fac, locked_sec):
1082
+ base = set(self._all_starts(task))
1083
+ for offset in range(task.duration):
1084
+ for s in locked_fac.get(task.faculty.id, set()):
1085
+ base.discard(s - offset)
1086
+ for s in locked_sec.get(task.section.section_id, set()):
1087
+ base.discard(s - offset)
1088
+
1089
+ for c in constraints:
1090
+ ctype = c.get('type', '').upper()
1091
+ if ctype == 'FACULTY_UNAVAILABLE' and c.get('faculty_id') == task.faculty.id:
1092
+ days = [DAY_INDEX[d] for d in (c.get('days') or []) if d in DAY_INDEX]
1093
+ for day in days:
1094
+ off = day * const.NUM_TEACHING_SLOTS_PER_DAY
1095
+ for p in range(const.NUM_TEACHING_SLOTS_PER_DAY):
1096
+ for o in range(task.duration): base.discard(off + p - o)
1097
+ elif ctype == 'SUBJECT_PREFERRED_TIME':
1098
+ if c.get('subject_code') == task.subject.subject_code:
1099
+ period = str(c.get('period','')).upper()
1100
+ target = MORNING_PERIODS if 'MORNING' in period else AFTERNOON_PERIODS
1101
+ if target:
1102
+ base = {s for s in base if _period(s) in target}
1103
+ elif ctype == 'NO_FREE_PERIOD':
1104
+ required_periods = c.get('periods') or []
1105
+ sid = c.get('section_id')
1106
+ # Only enforce for this task's section if section filter set
1107
+ if required_periods and (not sid or task.section.section_id == sid):
1108
+ # Prefer slots in required period — don't fully restrict
1109
+ # (hard enforcement done via AddAtLeastOne in solver)
1110
+ pass
1111
+
1112
+ elif ctype == 'WORKING_DAYS':
1113
+ allowed_days = {DAY_INDEX[d] for d in (c.get('days') or [])
1114
+ if d in DAY_INDEX}
1115
+ if allowed_days:
1116
+ base = {s for s in base if _day(s) in allowed_days}
1117
+ return list(base)
1118
+
1119
+ def _allowed_rooms(self, task):
1120
+ allowed = [i for i, r in enumerate(self.rooms)
1121
+ if ((task.subject.subject_type == SubjectType.LAB) == r.is_lab
1122
+ and r.capacity >= task.section.student_strength)]
1123
+ return allowed if allowed else [0]
1124
+
1125
+ def _fail(self, msg):
1126
+ return ('NO_CHANGE', self.current_solution, [], f'❌ {msg}')
pending_timetables.json ADDED
The diff for this file is too large to render. See raw diff
 
reoptimizer.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # reoptimizer.py
2
+
3
+ """
4
+ This module handles emergency re-optimization of an existing timetable.
5
+ It is used when a disruption occurs (e.g., Faculty Leave) and the schedule
6
+ must be adjusted with minimal changes to the rest of the institution.
7
+
8
+ Strategy:
9
+ 1. Accept the original solution.
10
+ 2. Identify which Sections are directly affected by the disruption.
11
+ 3. "Freeze" (Fixed Constraint) all tasks belonging to unaffected sections.
12
+ 4. "Relax" (Variable) tasks belonging to affected sections, allowing them to swap slots.
13
+ 5. Add a penalty for every task that changes from its original slot to minimize disruption.
14
+ """
15
+
16
+ from typing import List, Dict, Any, Tuple, Set
17
+ from ortools.sat.python import cp_model
18
+
19
+ # Import project components
20
+ from models import Task, Faculty, Section, Room
21
+ from constraint_engine import ConstraintEngine
22
+ import constants as const
23
+
24
+ class EmergencyReoptimizer:
25
+ """
26
+ Specialized solver for adjusting existing timetables under new constraints.
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ tasks: List[Task],
32
+ faculties: List[Faculty],
33
+ sections: List[Section],
34
+ rooms: List[Room]
35
+ ):
36
+ self.tasks = tasks
37
+ self.faculties = faculties
38
+ self.sections = sections
39
+ self.rooms = rooms
40
+
41
+ # Internal lookups
42
+ self.tasks_by_id = {t.task_id: t for t in tasks}
43
+
44
+ def reoptimize_for_faculty_leave(
45
+ self,
46
+ current_schedule: Dict[str, Dict[str, Any]],
47
+ faculty_id: str,
48
+ leave_day_index: int,
49
+ time_limit_seconds: int = 30
50
+ ) -> Tuple[str, Dict[str, Any]]:
51
+ """
52
+ Adjusts the schedule to accommodate a faculty member being unavailable on a specific day.
53
+
54
+ Args:
55
+ current_schedule: The output dictionary from the original Solver.
56
+ faculty_id: The ID of the faculty on leave.
57
+ leave_day_index: The day index (0=Mon, 4=Fri) of the leave.
58
+ time_limit_seconds: Max time for re-solving.
59
+
60
+ Returns:
61
+ Tuple (status, new_schedule)
62
+ """
63
+ print(f"--- Starting Emergency Re-optimization: Faculty {faculty_id} on Day {leave_day_index} ---")
64
+
65
+ # 1. Initialize a fresh model
66
+ model = cp_model.CpModel()
67
+
68
+ # 2. Initialize Constraint Engine (applies all HARD constraints: limits, rooms, etc.)
69
+ # We assume the Faculty object's availability hasn't been permanently changed in the DB,
70
+ # so we will add the leave constraint manually below.
71
+ ce = ConstraintEngine(model, self.tasks, self.faculties, self.sections, self.rooms)
72
+ ce.apply_all_constraints()
73
+
74
+ # 3. Apply the Emergency Constraint: Faculty Unavailable on Leave Day
75
+ self._apply_leave_constraint(model, ce, faculty_id, leave_day_index)
76
+
77
+ # 4. Identify Affected Sections
78
+ # We need to unfreeze any section that has a class with this faculty on that day,
79
+ # so the solver can swap that class with another class from a different day.
80
+ affected_section_ids = self._identify_affected_sections(current_schedule, faculty_id, leave_day_index)
81
+ print(f"Affected Sections (Schedule will be relaxed): {affected_section_ids}")
82
+
83
+ # 5. Apply Freezing and Change Penalties
84
+ self._freeze_and_relax_variables(model, ce, current_schedule, affected_section_ids)
85
+
86
+ # 6. Solve
87
+ solver = cp_model.CpSolver()
88
+ solver.parameters.max_time_in_seconds = time_limit_seconds
89
+ solver.parameters.num_search_workers = 8
90
+
91
+ status_val = solver.Solve(model)
92
+
93
+ status_map = {
94
+ cp_model.OPTIMAL: "OPTIMAL",
95
+ cp_model.FEASIBLE: "FEASIBLE",
96
+ cp_model.INFEASIBLE: "INFEASIBLE",
97
+ cp_model.MODEL_INVALID: "MODEL_INVALID",
98
+ cp_model.UNKNOWN: "UNKNOWN"
99
+ }
100
+ status_str = status_map.get(status_val, "UNKNOWN")
101
+ print(f"Re-optimization finished: {status_str}")
102
+
103
+ if status_val in [cp_model.OPTIMAL, cp_model.FEASIBLE]:
104
+ return status_str, self._extract_solution(solver, ce)
105
+ else:
106
+ print("Could not find a valid solution for the emergency constraint.")
107
+ return status_str, None
108
+
109
+ def _apply_leave_constraint(self, model: cp_model.CpModel, ce: ConstraintEngine, faculty_id: str, day_index: int):
110
+ """
111
+ Hard constraint: The specific faculty cannot teach on the specific day.
112
+ """
113
+ day_start_slot = day_index * const.NUM_TEACHING_SLOTS_PER_DAY
114
+ day_end_slot = (day_index + 1) * const.NUM_TEACHING_SLOTS_PER_DAY
115
+
116
+ # Iterate over all tasks taught by this faculty
117
+ for task in self.tasks:
118
+ if task.faculty.id == faculty_id:
119
+ start_var = ce.task_vars[task.task_id][0]
120
+
121
+ # Logic: Task cannot start within the day's range.
122
+ # Since tasks are interval vars, we technically check overlap,
123
+ # but start_time check is usually sufficient for single-day constraint
124
+ # if duration doesn't span days (which it doesn't).
125
+
126
+ # We enforce: start_var < day_start OR start_var >= day_end
127
+ # This effectively bans the task from that day.
128
+
129
+ # Using boolean indicators to enforce the "OR" logic
130
+ before_day = model.NewBoolVar(f"{task.task_id}_before_leave")
131
+ after_day = model.NewBoolVar(f"{task.task_id}_after_leave")
132
+
133
+ model.Add(start_var < day_start_slot).OnlyEnforceIf(before_day)
134
+ model.Add(start_var >= day_end_slot).OnlyEnforceIf(after_day)
135
+
136
+ # Must be either before OR after (cannot be during)
137
+ model.AddBoolOr([before_day, after_day])
138
+
139
+ def _identify_affected_sections(self, schedule: Dict, faculty_id: str, day_index: int) -> Set[str]:
140
+ """
141
+ Identifies which sections have a class with the faculty on the leave day.
142
+ These are the sections that need their schedules relaxed to allow swapping.
143
+ """
144
+ affected_sections = set()
145
+
146
+ for task_id, data in schedule.items():
147
+ # Check if this task is taught by the faculty on the specific day
148
+ if data['faculty_name'] == self.tasks_by_id[task_id].faculty.name: # Ideally match ID
149
+ # We use the faculty object from the task list to be safe
150
+ task_fac_id = self.tasks_by_id[task_id].faculty.id
151
+
152
+ if task_fac_id == faculty_id and data['day_index'] == day_index:
153
+ affected_sections.add(data['section_id'])
154
+
155
+ return affected_sections
156
+
157
+ def _freeze_and_relax_variables(
158
+ self,
159
+ model: cp_model.CpModel,
160
+ ce: ConstraintEngine,
161
+ current_schedule: Dict,
162
+ affected_section_ids: Set[str]
163
+ ):
164
+ """
165
+ Freezes tasks for unaffected sections.
166
+ Adds penalties for changing tasks for affected sections.
167
+ """
168
+ change_vars = []
169
+
170
+ for task in self.tasks:
171
+ # Get the CP variables for this task
172
+ start_var, _, _, room_var = ce.task_vars[task.task_id]
173
+
174
+ # Get original values
175
+ if task.task_id in current_schedule:
176
+ original_start = current_schedule[task.task_id]['start_slot']
177
+ original_room_id = current_schedule[task.task_id]['room_id']
178
+ # Map room ID back to index
179
+ original_room_idx = ce.room_map[original_room_id]
180
+ else:
181
+ # Should not happen in a valid existing schedule, but safe fallback
182
+ continue
183
+
184
+ if task.section.section_id not in affected_section_ids:
185
+ # --- STRATEGY A: FREEZE UNAFFECTED ---
186
+ # This task belongs to a section that is NOT affected by the leave.
187
+ # Its schedule should remain exactly the same to preserve stability.
188
+ model.Add(start_var == original_start)
189
+ model.Add(room_var == original_room_idx)
190
+ else:
191
+ # --- STRATEGY B: RELAX AND PENALIZE AFFECTED ---
192
+ # This task belongs to a section that needs to reshuffle.
193
+ # We allow it to move, but we penalize it if it does.
194
+
195
+ # Boolean: Is the new start time different from the old start time?
196
+ is_changed = model.NewBoolVar(f"changed_{task.task_id}")
197
+ model.Add(start_var != original_start).OnlyEnforceIf(is_changed)
198
+ model.Add(start_var == original_start).OnlyEnforceIf(is_changed.Not())
199
+
200
+ # Weight the penalty.
201
+ # If this specific task is the one causing the conflict (taught by absent faculty),
202
+ # it MUST change, so the penalty is inevitable (and ignored by solver logic essentially).
203
+ # For other tasks (swapping candidates), the penalty discourages unnecessary moves.
204
+ change_vars.append(is_changed)
205
+
206
+ # Minimize the total number of tasks moved
207
+ if change_vars:
208
+ model.Minimize(sum(change_vars))
209
+
210
+ def _extract_solution(self, solver: cp_model.CpSolver, ce: ConstraintEngine) -> Dict:
211
+ """
212
+ Extracts the new schedule similar to the main solver.
213
+ """
214
+ schedule = {}
215
+ for task in self.tasks:
216
+ start_var, _, _, room_var = ce.task_vars[task.task_id]
217
+
218
+ start_slot = solver.Value(start_var)
219
+ room_index = solver.Value(room_var)
220
+ assigned_room = self.rooms[room_index]
221
+
222
+ day_index = start_slot // const.NUM_TEACHING_SLOTS_PER_DAY
223
+ daily_slot_index = start_slot % const.NUM_TEACHING_SLOTS_PER_DAY
224
+
225
+ schedule[task.task_id] = {
226
+ "task_obj": task,
227
+ "start_slot": start_slot,
228
+ "day_index": day_index,
229
+ "day_name": const.DAYS[day_index],
230
+ "period_index": daily_slot_index,
231
+ "room_id": assigned_room.room_id,
232
+ "room_name": f"{assigned_room.room_id} ({assigned_room.building})",
233
+ "faculty_name": task.faculty.name,
234
+ "subject_code": task.subject.subject_code,
235
+ "section_id": task.section.section_id,
236
+ "duration": task.duration
237
+ }
238
+ return schedule
requirements.txt ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ streamlit
2
+ fastapi
3
+ uvicorn[standard]
4
+ ortools
5
+ pandas
6
+ requests
7
+ pydantic
8
+ huggingface_hub
9
+
10
+ python-multipart
11
+ openpyxl
saved_schedule.json ADDED
@@ -0,0 +1,2585 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "generated_at": "2026-11-04T18:30:49.974816",
3
+ "schedule": {
4
+ "DL&RL-7A-0": {
5
+ "start_slot": 24,
6
+ "day_index": 3,
7
+ "day_name": "Thursday",
8
+ "period_index": 0,
9
+ "room_id": "AI09 1F 25",
10
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
11
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
12
+ "subject_code": "DL&RL",
13
+ "section_id": "7A",
14
+ "duration": 1
15
+ },
16
+ "DL&RL-7A-1": {
17
+ "start_slot": 17,
18
+ "day_index": 2,
19
+ "day_name": "Wednesday",
20
+ "period_index": 1,
21
+ "room_id": "AI09 1F 25",
22
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
23
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
24
+ "subject_code": "DL&RL",
25
+ "section_id": "7A",
26
+ "duration": 1
27
+ },
28
+ "DL&RL-7A-2": {
29
+ "start_slot": 35,
30
+ "day_index": 4,
31
+ "day_name": "Friday",
32
+ "period_index": 3,
33
+ "room_id": "AI09 2F 14",
34
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
35
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
36
+ "subject_code": "DL&RL",
37
+ "section_id": "7A",
38
+ "duration": 1
39
+ },
40
+ "DL&RL-7B-0": {
41
+ "start_slot": 11,
42
+ "day_index": 1,
43
+ "day_name": "Tuesday",
44
+ "period_index": 3,
45
+ "room_id": "AI09 1F 25",
46
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
47
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
48
+ "subject_code": "DL&RL",
49
+ "section_id": "7B",
50
+ "duration": 1
51
+ },
52
+ "DL&RL-7B-1": {
53
+ "start_slot": 3,
54
+ "day_index": 0,
55
+ "day_name": "Monday",
56
+ "period_index": 3,
57
+ "room_id": "AI09 3F 01",
58
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
59
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
60
+ "subject_code": "DL&RL",
61
+ "section_id": "7B",
62
+ "duration": 1
63
+ },
64
+ "DL&RL-7B-2": {
65
+ "start_slot": 26,
66
+ "day_index": 3,
67
+ "day_name": "Thursday",
68
+ "period_index": 2,
69
+ "room_id": "AI09 1F26",
70
+ "room_name": "AI09 1F26 (ECE BLOCK)",
71
+ "faculty_name": "Vijayashekar S S / Nandita Tarikeri",
72
+ "subject_code": "DL&RL",
73
+ "section_id": "7B",
74
+ "duration": 1
75
+ },
76
+ "DL&RL Lab-7A-B1-BLOCK": {
77
+ "start_slot": 6,
78
+ "day_index": 0,
79
+ "day_name": "Monday",
80
+ "period_index": 6,
81
+ "room_id": "AI09 3F 03",
82
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
83
+ "faculty_name": "Dr. Kavitha Nair R",
84
+ "subject_code": "DL&RL Lab",
85
+ "section_id": "7A-B1",
86
+ "duration": 2
87
+ },
88
+ "DL&RL Lab-7A-B2-BLOCK": {
89
+ "start_slot": 8,
90
+ "day_index": 1,
91
+ "day_name": "Tuesday",
92
+ "period_index": 0,
93
+ "room_id": "AI09 2F 13",
94
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
95
+ "faculty_name": "Dr. Kavitha Nair R",
96
+ "subject_code": "DL&RL Lab",
97
+ "section_id": "7A-B2",
98
+ "duration": 2
99
+ },
100
+ "DL&RL Lab-7B-B1-BLOCK": {
101
+ "start_slot": 22,
102
+ "day_index": 2,
103
+ "day_name": "Wednesday",
104
+ "period_index": 6,
105
+ "room_id": "AI09 2F 14",
106
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
107
+ "faculty_name": "Mr. Praveen Arokia Raj",
108
+ "subject_code": "DL&RL Lab",
109
+ "section_id": "7B-B1",
110
+ "duration": 2
111
+ },
112
+ "DL&RL Lab-7B-B2-BLOCK": {
113
+ "start_slot": 24,
114
+ "day_index": 3,
115
+ "day_name": "Thursday",
116
+ "period_index": 0,
117
+ "room_id": "AI09 3F 08",
118
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
119
+ "faculty_name": "Mr. Praveen Arokia Raj",
120
+ "subject_code": "DL&RL Lab",
121
+ "section_id": "7B-B2",
122
+ "duration": 2
123
+ },
124
+ "ML II-7A-0": {
125
+ "start_slot": 11,
126
+ "day_index": 1,
127
+ "day_name": "Tuesday",
128
+ "period_index": 3,
129
+ "room_id": "AI09 3F 03",
130
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
131
+ "faculty_name": "Mr. Syed Musadiq Illahi",
132
+ "subject_code": "ML II",
133
+ "section_id": "7A",
134
+ "duration": 1
135
+ },
136
+ "ML II-7A-1": {
137
+ "start_slot": 1,
138
+ "day_index": 0,
139
+ "day_name": "Monday",
140
+ "period_index": 1,
141
+ "room_id": "AI09 1F26",
142
+ "room_name": "AI09 1F26 (ECE BLOCK)",
143
+ "faculty_name": "Mr. Syed Musadiq Illahi",
144
+ "subject_code": "ML II",
145
+ "section_id": "7A",
146
+ "duration": 1
147
+ },
148
+ "ML II-7A-2": {
149
+ "start_slot": 27,
150
+ "day_index": 3,
151
+ "day_name": "Thursday",
152
+ "period_index": 3,
153
+ "room_id": "AI09 1F 25",
154
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
155
+ "faculty_name": "Mr. Syed Musadiq Illahi",
156
+ "subject_code": "ML II",
157
+ "section_id": "7A",
158
+ "duration": 1
159
+ },
160
+ "ML II-7B-0": {
161
+ "start_slot": 19,
162
+ "day_index": 2,
163
+ "day_name": "Wednesday",
164
+ "period_index": 3,
165
+ "room_id": "AI09 2F 13",
166
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
167
+ "faculty_name": "Mr. Abhijith S",
168
+ "subject_code": "ML II",
169
+ "section_id": "7B",
170
+ "duration": 1
171
+ },
172
+ "ML II-7B-1": {
173
+ "start_slot": 1,
174
+ "day_index": 0,
175
+ "day_name": "Monday",
176
+ "period_index": 1,
177
+ "room_id": "AI09 3F 08",
178
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
179
+ "faculty_name": "Mr. Abhijith S",
180
+ "subject_code": "ML II",
181
+ "section_id": "7B",
182
+ "duration": 1
183
+ },
184
+ "ML II-7B-2": {
185
+ "start_slot": 35,
186
+ "day_index": 4,
187
+ "day_name": "Friday",
188
+ "period_index": 3,
189
+ "room_id": "AI09 1F26",
190
+ "room_name": "AI09 1F26 (ECE BLOCK)",
191
+ "faculty_name": "Mr. Abhijith S",
192
+ "subject_code": "ML II",
193
+ "section_id": "7B",
194
+ "duration": 1
195
+ },
196
+ "ML II Lab-7A-B1-BLOCK": {
197
+ "start_slot": 8,
198
+ "day_index": 1,
199
+ "day_name": "Tuesday",
200
+ "period_index": 0,
201
+ "room_id": "AI09 3F 08",
202
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
203
+ "faculty_name": "Mr. Syed Musadiq Illahi",
204
+ "subject_code": "ML II Lab",
205
+ "section_id": "7A-B1",
206
+ "duration": 2
207
+ },
208
+ "ML II Lab-7A-B2-BLOCK": {
209
+ "start_slot": 6,
210
+ "day_index": 0,
211
+ "day_name": "Monday",
212
+ "period_index": 6,
213
+ "room_id": "AI09 3F 08",
214
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
215
+ "faculty_name": "Mr. Syed Musadiq Illahi",
216
+ "subject_code": "ML II Lab",
217
+ "section_id": "7A-B2",
218
+ "duration": 2
219
+ },
220
+ "ML II Lab-7B-B1-BLOCK": {
221
+ "start_slot": 24,
222
+ "day_index": 3,
223
+ "day_name": "Thursday",
224
+ "period_index": 0,
225
+ "room_id": "AI09 2F 14",
226
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
227
+ "faculty_name": "Mr. Abhijith S",
228
+ "subject_code": "ML II Lab",
229
+ "section_id": "7B-B1",
230
+ "duration": 2
231
+ },
232
+ "ML II Lab-7B-B2-BLOCK": {
233
+ "start_slot": 22,
234
+ "day_index": 2,
235
+ "day_name": "Wednesday",
236
+ "period_index": 6,
237
+ "room_id": "AI09 1F 25",
238
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
239
+ "faculty_name": "Mr. Abhijith S",
240
+ "subject_code": "ML II Lab",
241
+ "section_id": "7B-B2",
242
+ "duration": 2
243
+ },
244
+ "DS&P-7A-0": {
245
+ "start_slot": 3,
246
+ "day_index": 0,
247
+ "day_name": "Monday",
248
+ "period_index": 3,
249
+ "room_id": "AI09 2F 14",
250
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
251
+ "faculty_name": "Mr. Jovin Deglus",
252
+ "subject_code": "DS&P",
253
+ "section_id": "7A",
254
+ "duration": 1
255
+ },
256
+ "DS&P-7A-1": {
257
+ "start_slot": 32,
258
+ "day_index": 4,
259
+ "day_name": "Friday",
260
+ "period_index": 0,
261
+ "room_id": "AI09 2F 14",
262
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
263
+ "faculty_name": "Mr. Jovin Deglus",
264
+ "subject_code": "DS&P",
265
+ "section_id": "7A",
266
+ "duration": 1
267
+ },
268
+ "DS&P-7A-2": {
269
+ "start_slot": 16,
270
+ "day_index": 2,
271
+ "day_name": "Wednesday",
272
+ "period_index": 0,
273
+ "room_id": "AI09 3F 08",
274
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
275
+ "faculty_name": "Mr. Jovin Deglus",
276
+ "subject_code": "DS&P",
277
+ "section_id": "7A",
278
+ "duration": 1
279
+ },
280
+ "DS&P-7A-3": {
281
+ "start_slot": 26,
282
+ "day_index": 3,
283
+ "day_name": "Thursday",
284
+ "period_index": 2,
285
+ "room_id": "AI09 3F 03",
286
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
287
+ "faculty_name": "Mr. Jovin Deglus",
288
+ "subject_code": "DS&P",
289
+ "section_id": "7A",
290
+ "duration": 1
291
+ },
292
+ "DS&P-7B-0": {
293
+ "start_slot": 27,
294
+ "day_index": 3,
295
+ "day_name": "Thursday",
296
+ "period_index": 3,
297
+ "room_id": "AI09 2F 12",
298
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
299
+ "faculty_name": "Mr. Jovin Deglus",
300
+ "subject_code": "DS&P",
301
+ "section_id": "7B",
302
+ "duration": 1
303
+ },
304
+ "DS&P-7B-1": {
305
+ "start_slot": 9,
306
+ "day_index": 1,
307
+ "day_name": "Tuesday",
308
+ "period_index": 1,
309
+ "room_id": "AI09 3F 01",
310
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
311
+ "faculty_name": "Mr. Jovin Deglus",
312
+ "subject_code": "DS&P",
313
+ "section_id": "7B",
314
+ "duration": 1
315
+ },
316
+ "DS&P-7B-2": {
317
+ "start_slot": 17,
318
+ "day_index": 2,
319
+ "day_name": "Wednesday",
320
+ "period_index": 1,
321
+ "room_id": "AI09 2F 08",
322
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
323
+ "faculty_name": "Mr. Jovin Deglus",
324
+ "subject_code": "DS&P",
325
+ "section_id": "7B",
326
+ "duration": 1
327
+ },
328
+ "DS&P-7B-3": {
329
+ "start_slot": 34,
330
+ "day_index": 4,
331
+ "day_name": "Friday",
332
+ "period_index": 2,
333
+ "room_id": "AI09 3F 08",
334
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
335
+ "faculty_name": "Mr. Jovin Deglus",
336
+ "subject_code": "DS&P",
337
+ "section_id": "7B",
338
+ "duration": 1
339
+ },
340
+ "BA-7A-0": {
341
+ "start_slot": 5,
342
+ "day_index": 0,
343
+ "day_name": "Monday",
344
+ "period_index": 5,
345
+ "room_id": "AI09 3F 01",
346
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
347
+ "faculty_name": "MBA Faculty",
348
+ "subject_code": "BA",
349
+ "section_id": "7A",
350
+ "duration": 1
351
+ },
352
+ "BA-7A-1": {
353
+ "start_slot": 31,
354
+ "day_index": 3,
355
+ "day_name": "Thursday",
356
+ "period_index": 7,
357
+ "room_id": "AI09 3F 03",
358
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
359
+ "faculty_name": "MBA Faculty",
360
+ "subject_code": "BA",
361
+ "section_id": "7A",
362
+ "duration": 1
363
+ },
364
+ "BA-7A-2": {
365
+ "start_slot": 33,
366
+ "day_index": 4,
367
+ "day_name": "Friday",
368
+ "period_index": 1,
369
+ "room_id": "AI09 3F 01",
370
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
371
+ "faculty_name": "MBA Faculty",
372
+ "subject_code": "BA",
373
+ "section_id": "7A",
374
+ "duration": 1
375
+ },
376
+ "BA-7B-0": {
377
+ "start_slot": 5,
378
+ "day_index": 0,
379
+ "day_name": "Monday",
380
+ "period_index": 5,
381
+ "room_id": "AI09 2F 08",
382
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
383
+ "faculty_name": "MBA Faculty",
384
+ "subject_code": "BA",
385
+ "section_id": "7B",
386
+ "duration": 1
387
+ },
388
+ "BA-7B-1": {
389
+ "start_slot": 31,
390
+ "day_index": 3,
391
+ "day_name": "Thursday",
392
+ "period_index": 7,
393
+ "room_id": "AI09 2F 14",
394
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
395
+ "faculty_name": "MBA Faculty",
396
+ "subject_code": "BA",
397
+ "section_id": "7B",
398
+ "duration": 1
399
+ },
400
+ "BA-7B-2": {
401
+ "start_slot": 33,
402
+ "day_index": 4,
403
+ "day_name": "Friday",
404
+ "period_index": 1,
405
+ "room_id": "AI09 3F 08",
406
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
407
+ "faculty_name": "MBA Faculty",
408
+ "subject_code": "BA",
409
+ "section_id": "7B",
410
+ "duration": 1
411
+ },
412
+ "BA-7DS-0": {
413
+ "start_slot": 5,
414
+ "day_index": 0,
415
+ "day_name": "Monday",
416
+ "period_index": 5,
417
+ "room_id": "AI09 2F 13",
418
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
419
+ "faculty_name": "Mr. Yogesh Dixith",
420
+ "subject_code": "BA",
421
+ "section_id": "7DS",
422
+ "duration": 1
423
+ },
424
+ "BA-7DS-1": {
425
+ "start_slot": 31,
426
+ "day_index": 3,
427
+ "day_name": "Thursday",
428
+ "period_index": 7,
429
+ "room_id": "AI09 2F 12",
430
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
431
+ "faculty_name": "Mr. Yogesh Dixith",
432
+ "subject_code": "BA",
433
+ "section_id": "7DS",
434
+ "duration": 1
435
+ },
436
+ "BA-7DS-2": {
437
+ "start_slot": 33,
438
+ "day_index": 4,
439
+ "day_name": "Friday",
440
+ "period_index": 1,
441
+ "room_id": "AI09 2F 12",
442
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
443
+ "faculty_name": "Mr. Yogesh Dixith",
444
+ "subject_code": "BA",
445
+ "section_id": "7DS",
446
+ "duration": 1
447
+ },
448
+ "BDA-7A-0": {
449
+ "start_slot": 5,
450
+ "day_index": 0,
451
+ "day_name": "Monday",
452
+ "period_index": 5,
453
+ "room_id": "AI09 1F 25",
454
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
455
+ "faculty_name": "Ms. Surbhi",
456
+ "subject_code": "BDA",
457
+ "section_id": "7A",
458
+ "duration": 1
459
+ },
460
+ "BDA-7A-1": {
461
+ "start_slot": 31,
462
+ "day_index": 3,
463
+ "day_name": "Thursday",
464
+ "period_index": 7,
465
+ "room_id": "AI09 2F 13",
466
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
467
+ "faculty_name": "Ms. Surbhi",
468
+ "subject_code": "BDA",
469
+ "section_id": "7A",
470
+ "duration": 1
471
+ },
472
+ "BDA-7A-2": {
473
+ "start_slot": 33,
474
+ "day_index": 4,
475
+ "day_name": "Friday",
476
+ "period_index": 1,
477
+ "room_id": "AI09 1F26",
478
+ "room_name": "AI09 1F26 (ECE BLOCK)",
479
+ "faculty_name": "Ms. Surbhi",
480
+ "subject_code": "BDA",
481
+ "section_id": "7A",
482
+ "duration": 1
483
+ },
484
+ "BDA-7B-0": {
485
+ "start_slot": 5,
486
+ "day_index": 0,
487
+ "day_name": "Monday",
488
+ "period_index": 5,
489
+ "room_id": "AI09 2F 12",
490
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
491
+ "faculty_name": "Ms. Surbhi",
492
+ "subject_code": "BDA",
493
+ "section_id": "7B",
494
+ "duration": 1
495
+ },
496
+ "BDA-7B-1": {
497
+ "start_slot": 31,
498
+ "day_index": 3,
499
+ "day_name": "Thursday",
500
+ "period_index": 7,
501
+ "room_id": "AI09 1F 25",
502
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
503
+ "faculty_name": "Ms. Surbhi",
504
+ "subject_code": "BDA",
505
+ "section_id": "7B",
506
+ "duration": 1
507
+ },
508
+ "BDA-7B-2": {
509
+ "start_slot": 33,
510
+ "day_index": 4,
511
+ "day_name": "Friday",
512
+ "period_index": 1,
513
+ "room_id": "AI09 1F 25",
514
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
515
+ "faculty_name": "Ms. Surbhi",
516
+ "subject_code": "BDA",
517
+ "section_id": "7B",
518
+ "duration": 1
519
+ },
520
+ "DL-7DS-0": {
521
+ "start_slot": 5,
522
+ "day_index": 0,
523
+ "day_name": "Monday",
524
+ "period_index": 5,
525
+ "room_id": "AI09 3F 08",
526
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
527
+ "faculty_name": "Mr. Nanda Kumar N",
528
+ "subject_code": "DL",
529
+ "section_id": "7DS",
530
+ "duration": 1
531
+ },
532
+ "DL-7DS-1": {
533
+ "start_slot": 31,
534
+ "day_index": 3,
535
+ "day_name": "Thursday",
536
+ "period_index": 7,
537
+ "room_id": "AI09 3F 01",
538
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
539
+ "faculty_name": "Mr. Nanda Kumar N",
540
+ "subject_code": "DL",
541
+ "section_id": "7DS",
542
+ "duration": 1
543
+ },
544
+ "DL-7DS-2": {
545
+ "start_slot": 33,
546
+ "day_index": 4,
547
+ "day_name": "Friday",
548
+ "period_index": 1,
549
+ "room_id": "AI09 2F 14",
550
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
551
+ "faculty_name": "Mr. Nanda Kumar N",
552
+ "subject_code": "DL",
553
+ "section_id": "7DS",
554
+ "duration": 1
555
+ },
556
+ "OE (Sem 7)-7A-0": {
557
+ "start_slot": 10,
558
+ "day_index": 1,
559
+ "day_name": "Tuesday",
560
+ "period_index": 2,
561
+ "room_id": "AI09 2F 08",
562
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
563
+ "faculty_name": "OE Teacher 1",
564
+ "subject_code": "OE (Sem 7)",
565
+ "section_id": "7A",
566
+ "duration": 1
567
+ },
568
+ "OE (Sem 7)-7A-1": {
569
+ "start_slot": 2,
570
+ "day_index": 0,
571
+ "day_name": "Monday",
572
+ "period_index": 2,
573
+ "room_id": "AI09 3F 08",
574
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
575
+ "faculty_name": "OE Teacher 1",
576
+ "subject_code": "OE (Sem 7)",
577
+ "section_id": "7A",
578
+ "duration": 1
579
+ },
580
+ "OE (Sem 7)-7A-2": {
581
+ "start_slot": 18,
582
+ "day_index": 2,
583
+ "day_name": "Wednesday",
584
+ "period_index": 2,
585
+ "room_id": "AI09 2F 12",
586
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
587
+ "faculty_name": "OE Teacher 1",
588
+ "subject_code": "OE (Sem 7)",
589
+ "section_id": "7A",
590
+ "duration": 1
591
+ },
592
+ "OE (Sem 7)-7B-0": {
593
+ "start_slot": 18,
594
+ "day_index": 2,
595
+ "day_name": "Wednesday",
596
+ "period_index": 2,
597
+ "room_id": "AI09 3F 03",
598
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
599
+ "faculty_name": "OE Teacher 2",
600
+ "subject_code": "OE (Sem 7)",
601
+ "section_id": "7B",
602
+ "duration": 1
603
+ },
604
+ "OE (Sem 7)-7B-1": {
605
+ "start_slot": 10,
606
+ "day_index": 1,
607
+ "day_name": "Tuesday",
608
+ "period_index": 2,
609
+ "room_id": "AI09 1F26",
610
+ "room_name": "AI09 1F26 (ECE BLOCK)",
611
+ "faculty_name": "OE Teacher 2",
612
+ "subject_code": "OE (Sem 7)",
613
+ "section_id": "7B",
614
+ "duration": 1
615
+ },
616
+ "OE (Sem 7)-7B-2": {
617
+ "start_slot": 2,
618
+ "day_index": 0,
619
+ "day_name": "Monday",
620
+ "period_index": 2,
621
+ "room_id": "AI09 3F 03",
622
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
623
+ "faculty_name": "OE Teacher 2",
624
+ "subject_code": "OE (Sem 7)",
625
+ "section_id": "7B",
626
+ "duration": 1
627
+ },
628
+ "OE (Sem 7)-7DS-0": {
629
+ "start_slot": 18,
630
+ "day_index": 2,
631
+ "day_name": "Wednesday",
632
+ "period_index": 2,
633
+ "room_id": "AI09 3F 08",
634
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
635
+ "faculty_name": "OE Teacher 3 (Sem 7)",
636
+ "subject_code": "OE (Sem 7)",
637
+ "section_id": "7DS",
638
+ "duration": 1
639
+ },
640
+ "OE (Sem 7)-7DS-1": {
641
+ "start_slot": 2,
642
+ "day_index": 0,
643
+ "day_name": "Monday",
644
+ "period_index": 2,
645
+ "room_id": "AI09 1F26",
646
+ "room_name": "AI09 1F26 (ECE BLOCK)",
647
+ "faculty_name": "OE Teacher 3 (Sem 7)",
648
+ "subject_code": "OE (Sem 7)",
649
+ "section_id": "7DS",
650
+ "duration": 1
651
+ },
652
+ "OE (Sem 7)-7DS-2": {
653
+ "start_slot": 10,
654
+ "day_index": 1,
655
+ "day_name": "Tuesday",
656
+ "period_index": 2,
657
+ "room_id": "AI09 1F 25",
658
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
659
+ "faculty_name": "OE Teacher 3 (Sem 7)",
660
+ "subject_code": "OE (Sem 7)",
661
+ "section_id": "7DS",
662
+ "duration": 1
663
+ },
664
+ "PP-7DS-0": {
665
+ "start_slot": 16,
666
+ "day_index": 2,
667
+ "day_name": "Wednesday",
668
+ "period_index": 0,
669
+ "room_id": "AI09 1F 25",
670
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
671
+ "faculty_name": "Mr. Chalasani Jayanth",
672
+ "subject_code": "PP",
673
+ "section_id": "7DS",
674
+ "duration": 1
675
+ },
676
+ "PP-7DS-1": {
677
+ "start_slot": 3,
678
+ "day_index": 0,
679
+ "day_name": "Monday",
680
+ "period_index": 3,
681
+ "room_id": "AI09 1F26",
682
+ "room_name": "AI09 1F26 (ECE BLOCK)",
683
+ "faculty_name": "Mr. Chalasani Jayanth",
684
+ "subject_code": "PP",
685
+ "section_id": "7DS",
686
+ "duration": 1
687
+ },
688
+ "PP-7DS-2": {
689
+ "start_slot": 34,
690
+ "day_index": 4,
691
+ "day_name": "Friday",
692
+ "period_index": 2,
693
+ "room_id": "AI09 3F 01",
694
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
695
+ "faculty_name": "Mr. Chalasani Jayanth",
696
+ "subject_code": "PP",
697
+ "section_id": "7DS",
698
+ "duration": 1
699
+ },
700
+ "PP Lab-7DS-B1-BLOCK": {
701
+ "start_slot": 38,
702
+ "day_index": 4,
703
+ "day_name": "Friday",
704
+ "period_index": 6,
705
+ "room_id": "AI09 3F 01",
706
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
707
+ "faculty_name": "Mr. Chalasani Jayanth",
708
+ "subject_code": "PP Lab",
709
+ "section_id": "7DS-B1",
710
+ "duration": 2
711
+ },
712
+ "PP Lab-7DS-B2-BLOCK": {
713
+ "start_slot": 14,
714
+ "day_index": 1,
715
+ "day_name": "Tuesday",
716
+ "period_index": 6,
717
+ "room_id": "AI09 3F 01",
718
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
719
+ "faculty_name": "Mr. Chalasani Jayanth",
720
+ "subject_code": "PP Lab",
721
+ "section_id": "7DS-B2",
722
+ "duration": 2
723
+ },
724
+ "SML for DS-7DS-0": {
725
+ "start_slot": 32,
726
+ "day_index": 4,
727
+ "day_name": "Friday",
728
+ "period_index": 0,
729
+ "room_id": "AI09 3F 01",
730
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
731
+ "faculty_name": "Dr. Kavitha Nair R",
732
+ "subject_code": "SML for DS",
733
+ "section_id": "7DS",
734
+ "duration": 1
735
+ },
736
+ "SML for DS-7DS-1": {
737
+ "start_slot": 0,
738
+ "day_index": 0,
739
+ "day_name": "Monday",
740
+ "period_index": 0,
741
+ "room_id": "AI09 1F26",
742
+ "room_name": "AI09 1F26 (ECE BLOCK)",
743
+ "faculty_name": "Dr. Kavitha Nair R",
744
+ "subject_code": "SML for DS",
745
+ "section_id": "7DS",
746
+ "duration": 1
747
+ },
748
+ "SML for DS-7DS-2": {
749
+ "start_slot": 25,
750
+ "day_index": 3,
751
+ "day_name": "Thursday",
752
+ "period_index": 1,
753
+ "room_id": "AI09 3F 03",
754
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
755
+ "faculty_name": "Dr. Kavitha Nair R",
756
+ "subject_code": "SML for DS",
757
+ "section_id": "7DS",
758
+ "duration": 1
759
+ },
760
+ "SML for DS Lab-7DS-B1-BLOCK": {
761
+ "start_slot": 29,
762
+ "day_index": 3,
763
+ "day_name": "Thursday",
764
+ "period_index": 5,
765
+ "room_id": "AI09 1F 25",
766
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
767
+ "faculty_name": "Dr. Kavitha Nair R",
768
+ "subject_code": "SML for DS Lab",
769
+ "section_id": "7DS-B1",
770
+ "duration": 2
771
+ },
772
+ "SML for DS Lab-7DS-B2-BLOCK": {
773
+ "start_slot": 36,
774
+ "day_index": 4,
775
+ "day_name": "Friday",
776
+ "period_index": 4,
777
+ "room_id": "AI09 2F 13",
778
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
779
+ "faculty_name": "Dr. Kavitha Nair R",
780
+ "subject_code": "SML for DS Lab",
781
+ "section_id": "7DS-B2",
782
+ "duration": 2
783
+ },
784
+ "CNS-7DS-0": {
785
+ "start_slot": 19,
786
+ "day_index": 2,
787
+ "day_name": "Wednesday",
788
+ "period_index": 3,
789
+ "room_id": "AI09 3F 01",
790
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
791
+ "faculty_name": "Mr. Jovin Deglus",
792
+ "subject_code": "CNS",
793
+ "section_id": "7DS",
794
+ "duration": 1
795
+ },
796
+ "CNS-7DS-1": {
797
+ "start_slot": 8,
798
+ "day_index": 1,
799
+ "day_name": "Tuesday",
800
+ "period_index": 0,
801
+ "room_id": "AI09 2F 12",
802
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
803
+ "faculty_name": "Mr. Jovin Deglus",
804
+ "subject_code": "CNS",
805
+ "section_id": "7DS",
806
+ "duration": 1
807
+ },
808
+ "CNS-7DS-2": {
809
+ "start_slot": 1,
810
+ "day_index": 0,
811
+ "day_name": "Monday",
812
+ "period_index": 1,
813
+ "room_id": "AI09 2F 13",
814
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
815
+ "faculty_name": "Mr. Jovin Deglus",
816
+ "subject_code": "CNS",
817
+ "section_id": "7DS",
818
+ "duration": 1
819
+ },
820
+ "CNS-7DS-3": {
821
+ "start_slot": 35,
822
+ "day_index": 4,
823
+ "day_name": "Friday",
824
+ "period_index": 3,
825
+ "room_id": "AI09 1F 25",
826
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
827
+ "faculty_name": "Mr. Jovin Deglus",
828
+ "subject_code": "CNS",
829
+ "section_id": "7DS",
830
+ "duration": 1
831
+ },
832
+ "SEPM-5A-0": {
833
+ "start_slot": 17,
834
+ "day_index": 2,
835
+ "day_name": "Wednesday",
836
+ "period_index": 1,
837
+ "room_id": "AI09 2F 14",
838
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
839
+ "faculty_name": "NF3",
840
+ "subject_code": "SEPM",
841
+ "section_id": "5A",
842
+ "duration": 1
843
+ },
844
+ "SEPM-5A-1": {
845
+ "start_slot": 35,
846
+ "day_index": 4,
847
+ "day_name": "Friday",
848
+ "period_index": 3,
849
+ "room_id": "AI09 3F 03",
850
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
851
+ "faculty_name": "NF3",
852
+ "subject_code": "SEPM",
853
+ "section_id": "5A",
854
+ "duration": 1
855
+ },
856
+ "SEPM-5A-2": {
857
+ "start_slot": 2,
858
+ "day_index": 0,
859
+ "day_name": "Monday",
860
+ "period_index": 2,
861
+ "room_id": "AI09 1F 25",
862
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
863
+ "faculty_name": "NF3",
864
+ "subject_code": "SEPM",
865
+ "section_id": "5A",
866
+ "duration": 1
867
+ },
868
+ "SEPM-5A-3": {
869
+ "start_slot": 25,
870
+ "day_index": 3,
871
+ "day_name": "Thursday",
872
+ "period_index": 1,
873
+ "room_id": "AI09 1F26",
874
+ "room_name": "AI09 1F26 (ECE BLOCK)",
875
+ "faculty_name": "NF3",
876
+ "subject_code": "SEPM",
877
+ "section_id": "5A",
878
+ "duration": 1
879
+ },
880
+ "SEPM-5B-0": {
881
+ "start_slot": 16,
882
+ "day_index": 2,
883
+ "day_name": "Wednesday",
884
+ "period_index": 0,
885
+ "room_id": "AI09 3F 01",
886
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
887
+ "faculty_name": "NF3",
888
+ "subject_code": "SEPM",
889
+ "section_id": "5B",
890
+ "duration": 1
891
+ },
892
+ "SEPM-5B-1": {
893
+ "start_slot": 8,
894
+ "day_index": 1,
895
+ "day_name": "Tuesday",
896
+ "period_index": 0,
897
+ "room_id": "AI09 3F 03",
898
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
899
+ "faculty_name": "NF3",
900
+ "subject_code": "SEPM",
901
+ "section_id": "5B",
902
+ "duration": 1
903
+ },
904
+ "SEPM-5B-2": {
905
+ "start_slot": 1,
906
+ "day_index": 0,
907
+ "day_name": "Monday",
908
+ "period_index": 1,
909
+ "room_id": "AI09 2F 14",
910
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
911
+ "faculty_name": "NF3",
912
+ "subject_code": "SEPM",
913
+ "section_id": "5B",
914
+ "duration": 1
915
+ },
916
+ "SEPM-5B-3": {
917
+ "start_slot": 32,
918
+ "day_index": 4,
919
+ "day_name": "Friday",
920
+ "period_index": 0,
921
+ "room_id": "AI09 2F 13",
922
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
923
+ "faculty_name": "NF3",
924
+ "subject_code": "SEPM",
925
+ "section_id": "5B",
926
+ "duration": 1
927
+ },
928
+ "CN-5A-0": {
929
+ "start_slot": 1,
930
+ "day_index": 0,
931
+ "day_name": "Monday",
932
+ "period_index": 1,
933
+ "room_id": "AI09 3F 01",
934
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
935
+ "faculty_name": "Dr. S Anu Pallavi",
936
+ "subject_code": "CN",
937
+ "section_id": "5A",
938
+ "duration": 1
939
+ },
940
+ "CN-5A-1": {
941
+ "start_slot": 8,
942
+ "day_index": 1,
943
+ "day_name": "Tuesday",
944
+ "period_index": 0,
945
+ "room_id": "AI09 3F 01",
946
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
947
+ "faculty_name": "Dr. S Anu Pallavi",
948
+ "subject_code": "CN",
949
+ "section_id": "5A",
950
+ "duration": 1
951
+ },
952
+ "CN-5A-2": {
953
+ "start_slot": 34,
954
+ "day_index": 4,
955
+ "day_name": "Friday",
956
+ "period_index": 2,
957
+ "room_id": "AI09 2F 08",
958
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
959
+ "faculty_name": "Dr. S Anu Pallavi",
960
+ "subject_code": "CN",
961
+ "section_id": "5A",
962
+ "duration": 1
963
+ },
964
+ "CN-5B-0": {
965
+ "start_slot": 18,
966
+ "day_index": 2,
967
+ "day_name": "Wednesday",
968
+ "period_index": 2,
969
+ "room_id": "AI09 1F 25",
970
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
971
+ "faculty_name": "Dr. S Anu Pallavi",
972
+ "subject_code": "CN",
973
+ "section_id": "5B",
974
+ "duration": 1
975
+ },
976
+ "CN-5B-1": {
977
+ "start_slot": 3,
978
+ "day_index": 0,
979
+ "day_name": "Monday",
980
+ "period_index": 3,
981
+ "room_id": "AI09 3F 08",
982
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
983
+ "faculty_name": "Dr. S Anu Pallavi",
984
+ "subject_code": "CN",
985
+ "section_id": "5B",
986
+ "duration": 1
987
+ },
988
+ "CN-5B-2": {
989
+ "start_slot": 35,
990
+ "day_index": 4,
991
+ "day_name": "Friday",
992
+ "period_index": 3,
993
+ "room_id": "AI09 2F 13",
994
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
995
+ "faculty_name": "Dr. S Anu Pallavi",
996
+ "subject_code": "CN",
997
+ "section_id": "5B",
998
+ "duration": 1
999
+ },
1000
+ "CN Lab-5A-B1-BLOCK": {
1001
+ "start_slot": 12,
1002
+ "day_index": 1,
1003
+ "day_name": "Tuesday",
1004
+ "period_index": 4,
1005
+ "room_id": "AI09 2F 13",
1006
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1007
+ "faculty_name": "Dr. S Anu Pallavi",
1008
+ "subject_code": "CN Lab",
1009
+ "section_id": "5A-B1",
1010
+ "duration": 2
1011
+ },
1012
+ "CN Lab-5A-B2-BLOCK": {
1013
+ "start_slot": 20,
1014
+ "day_index": 2,
1015
+ "day_name": "Wednesday",
1016
+ "period_index": 4,
1017
+ "room_id": "AI09 3F 01",
1018
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1019
+ "faculty_name": "Dr. S Anu Pallavi",
1020
+ "subject_code": "CN Lab",
1021
+ "section_id": "5A-B2",
1022
+ "duration": 2
1023
+ },
1024
+ "CN Lab-5B-B1-BLOCK": {
1025
+ "start_slot": 6,
1026
+ "day_index": 0,
1027
+ "day_name": "Monday",
1028
+ "period_index": 6,
1029
+ "room_id": "AI09 2F 12",
1030
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1031
+ "faculty_name": "Dr. S Anu Pallavi",
1032
+ "subject_code": "CN Lab",
1033
+ "section_id": "5B-B1",
1034
+ "duration": 2
1035
+ },
1036
+ "CN Lab-5B-B2-BLOCK": {
1037
+ "start_slot": 26,
1038
+ "day_index": 3,
1039
+ "day_name": "Thursday",
1040
+ "period_index": 2,
1041
+ "room_id": "AI09 2F 14",
1042
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1043
+ "faculty_name": "Dr. S Anu Pallavi",
1044
+ "subject_code": "CN Lab",
1045
+ "section_id": "5B-B2",
1046
+ "duration": 2
1047
+ },
1048
+ "TOC-5A-0": {
1049
+ "start_slot": 18,
1050
+ "day_index": 2,
1051
+ "day_name": "Wednesday",
1052
+ "period_index": 2,
1053
+ "room_id": "AI09 2F 14",
1054
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1055
+ "faculty_name": "Ms. Vinutha M",
1056
+ "subject_code": "TOC",
1057
+ "section_id": "5A",
1058
+ "duration": 1
1059
+ },
1060
+ "TOC-5A-1": {
1061
+ "start_slot": 3,
1062
+ "day_index": 0,
1063
+ "day_name": "Monday",
1064
+ "period_index": 3,
1065
+ "room_id": "AI09 2F 12",
1066
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1067
+ "faculty_name": "Ms. Vinutha M",
1068
+ "subject_code": "TOC",
1069
+ "section_id": "5A",
1070
+ "duration": 1
1071
+ },
1072
+ "TOC-5A-2": {
1073
+ "start_slot": 11,
1074
+ "day_index": 1,
1075
+ "day_name": "Tuesday",
1076
+ "period_index": 3,
1077
+ "room_id": "AI09 2F 14",
1078
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1079
+ "faculty_name": "Ms. Vinutha M",
1080
+ "subject_code": "TOC",
1081
+ "section_id": "5A",
1082
+ "duration": 1
1083
+ },
1084
+ "TOC-5A-3": {
1085
+ "start_slot": 27,
1086
+ "day_index": 3,
1087
+ "day_name": "Thursday",
1088
+ "period_index": 3,
1089
+ "room_id": "AI09 2F 13",
1090
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1091
+ "faculty_name": "Ms. Vinutha M",
1092
+ "subject_code": "TOC",
1093
+ "section_id": "5A",
1094
+ "duration": 1
1095
+ },
1096
+ "TOC-5A-4": {
1097
+ "start_slot": 32,
1098
+ "day_index": 4,
1099
+ "day_name": "Friday",
1100
+ "period_index": 0,
1101
+ "room_id": "AI09 2F 08",
1102
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1103
+ "faculty_name": "Ms. Vinutha M",
1104
+ "subject_code": "TOC",
1105
+ "section_id": "5A",
1106
+ "duration": 1
1107
+ },
1108
+ "TOC-5B-0": {
1109
+ "start_slot": 25,
1110
+ "day_index": 3,
1111
+ "day_name": "Thursday",
1112
+ "period_index": 1,
1113
+ "room_id": "AI09 1F 25",
1114
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1115
+ "faculty_name": "Ms. Vinutha M",
1116
+ "subject_code": "TOC",
1117
+ "section_id": "5B",
1118
+ "duration": 1
1119
+ },
1120
+ "TOC-5B-1": {
1121
+ "start_slot": 10,
1122
+ "day_index": 1,
1123
+ "day_name": "Tuesday",
1124
+ "period_index": 2,
1125
+ "room_id": "AI09 3F 08",
1126
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
1127
+ "faculty_name": "Ms. Vinutha M",
1128
+ "subject_code": "TOC",
1129
+ "section_id": "5B",
1130
+ "duration": 1
1131
+ },
1132
+ "TOC-5B-2": {
1133
+ "start_slot": 2,
1134
+ "day_index": 0,
1135
+ "day_name": "Monday",
1136
+ "period_index": 2,
1137
+ "room_id": "AI09 3F 01",
1138
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1139
+ "faculty_name": "Ms. Vinutha M",
1140
+ "subject_code": "TOC",
1141
+ "section_id": "5B",
1142
+ "duration": 1
1143
+ },
1144
+ "TOC-5B-3": {
1145
+ "start_slot": 17,
1146
+ "day_index": 2,
1147
+ "day_name": "Wednesday",
1148
+ "period_index": 1,
1149
+ "room_id": "AI09 3F 01",
1150
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1151
+ "faculty_name": "Ms. Vinutha M",
1152
+ "subject_code": "TOC",
1153
+ "section_id": "5B",
1154
+ "duration": 1
1155
+ },
1156
+ "TOC-5B-4": {
1157
+ "start_slot": 34,
1158
+ "day_index": 4,
1159
+ "day_name": "Friday",
1160
+ "period_index": 2,
1161
+ "room_id": "AI09 2F 13",
1162
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1163
+ "faculty_name": "Ms. Vinutha M",
1164
+ "subject_code": "TOC",
1165
+ "section_id": "5B",
1166
+ "duration": 1
1167
+ },
1168
+ "DV Lab-5B-B1-BLOCK": {
1169
+ "start_slot": 36,
1170
+ "day_index": 4,
1171
+ "day_name": "Friday",
1172
+ "period_index": 4,
1173
+ "room_id": "AI03 2F 01",
1174
+ "room_name": "AI03 2F 01 (ASD BLOCK)",
1175
+ "faculty_name": "Jovin Deglus / Ranjan Kumar",
1176
+ "subject_code": "DV Lab",
1177
+ "section_id": "5B-B1",
1178
+ "duration": 2
1179
+ },
1180
+ "DV Lab-5B-B2-BLOCK": {
1181
+ "start_slot": 12,
1182
+ "day_index": 1,
1183
+ "day_name": "Tuesday",
1184
+ "period_index": 4,
1185
+ "room_id": "AI03 2F 01",
1186
+ "room_name": "AI03 2F 01 (ASD BLOCK)",
1187
+ "faculty_name": "Jovin Deglus / Ranjan Kumar",
1188
+ "subject_code": "DV Lab",
1189
+ "section_id": "5B-B2",
1190
+ "duration": 2
1191
+ },
1192
+ "CV-5A-0": {
1193
+ "start_slot": 14,
1194
+ "day_index": 1,
1195
+ "day_name": "Tuesday",
1196
+ "period_index": 6,
1197
+ "room_id": "AI09 2F 14",
1198
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1199
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1200
+ "subject_code": "CV",
1201
+ "section_id": "5A",
1202
+ "duration": 1
1203
+ },
1204
+ "CV-5A-1": {
1205
+ "start_slot": 4,
1206
+ "day_index": 0,
1207
+ "day_name": "Monday",
1208
+ "period_index": 4,
1209
+ "room_id": "AI09 3F 08",
1210
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
1211
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1212
+ "subject_code": "CV",
1213
+ "section_id": "5A",
1214
+ "duration": 1
1215
+ },
1216
+ "CV-5A-2": {
1217
+ "start_slot": 23,
1218
+ "day_index": 2,
1219
+ "day_name": "Wednesday",
1220
+ "period_index": 7,
1221
+ "room_id": "AI09 1F26",
1222
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1223
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1224
+ "subject_code": "CV",
1225
+ "section_id": "5A",
1226
+ "duration": 1
1227
+ },
1228
+ "EDA-5A-0": {
1229
+ "start_slot": 14,
1230
+ "day_index": 1,
1231
+ "day_name": "Tuesday",
1232
+ "period_index": 6,
1233
+ "room_id": "AI09 2F 12",
1234
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1235
+ "faculty_name": "Ms. Surbhi",
1236
+ "subject_code": "EDA",
1237
+ "section_id": "5A",
1238
+ "duration": 1
1239
+ },
1240
+ "EDA-5A-1": {
1241
+ "start_slot": 4,
1242
+ "day_index": 0,
1243
+ "day_name": "Monday",
1244
+ "period_index": 4,
1245
+ "room_id": "AI09 2F 14",
1246
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1247
+ "faculty_name": "Ms. Surbhi",
1248
+ "subject_code": "EDA",
1249
+ "section_id": "5A",
1250
+ "duration": 1
1251
+ },
1252
+ "EDA-5A-2": {
1253
+ "start_slot": 23,
1254
+ "day_index": 2,
1255
+ "day_name": "Wednesday",
1256
+ "period_index": 7,
1257
+ "room_id": "AI09 3F 03",
1258
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1259
+ "faculty_name": "Ms. Surbhi",
1260
+ "subject_code": "EDA",
1261
+ "section_id": "5A",
1262
+ "duration": 1
1263
+ },
1264
+ "CV-5B-0": {
1265
+ "start_slot": 14,
1266
+ "day_index": 1,
1267
+ "day_name": "Tuesday",
1268
+ "period_index": 6,
1269
+ "room_id": "AI09 2F 08",
1270
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1271
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1272
+ "subject_code": "CV",
1273
+ "section_id": "5B",
1274
+ "duration": 1
1275
+ },
1276
+ "CV-5B-1": {
1277
+ "start_slot": 4,
1278
+ "day_index": 0,
1279
+ "day_name": "Monday",
1280
+ "period_index": 4,
1281
+ "room_id": "AI09 1F 25",
1282
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1283
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1284
+ "subject_code": "CV",
1285
+ "section_id": "5B",
1286
+ "duration": 1
1287
+ },
1288
+ "CV-5B-2": {
1289
+ "start_slot": 23,
1290
+ "day_index": 2,
1291
+ "day_name": "Wednesday",
1292
+ "period_index": 7,
1293
+ "room_id": "AI09 3F 08",
1294
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
1295
+ "faculty_name": "Mr. Syed Musadiq Illahi",
1296
+ "subject_code": "CV",
1297
+ "section_id": "5B",
1298
+ "duration": 1
1299
+ },
1300
+ "EDA-5B-0": {
1301
+ "start_slot": 14,
1302
+ "day_index": 1,
1303
+ "day_name": "Tuesday",
1304
+ "period_index": 6,
1305
+ "room_id": "AI09 1F26",
1306
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1307
+ "faculty_name": "Ms. Surbhi",
1308
+ "subject_code": "EDA",
1309
+ "section_id": "5B",
1310
+ "duration": 1
1311
+ },
1312
+ "EDA-5B-1": {
1313
+ "start_slot": 4,
1314
+ "day_index": 0,
1315
+ "day_name": "Monday",
1316
+ "period_index": 4,
1317
+ "room_id": "AI09 2F 13",
1318
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1319
+ "faculty_name": "Ms. Surbhi",
1320
+ "subject_code": "EDA",
1321
+ "section_id": "5B",
1322
+ "duration": 1
1323
+ },
1324
+ "EDA-5B-2": {
1325
+ "start_slot": 23,
1326
+ "day_index": 2,
1327
+ "day_name": "Wednesday",
1328
+ "period_index": 7,
1329
+ "room_id": "AI09 2F 13",
1330
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1331
+ "faculty_name": "Ms. Surbhi",
1332
+ "subject_code": "EDA",
1333
+ "section_id": "5B",
1334
+ "duration": 1
1335
+ },
1336
+ "RM&IPR-5A-0": {
1337
+ "start_slot": 37,
1338
+ "day_index": 4,
1339
+ "day_name": "Friday",
1340
+ "period_index": 5,
1341
+ "room_id": "AI09 2F 14",
1342
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1343
+ "faculty_name": "Basavaraj",
1344
+ "subject_code": "RM&IPR",
1345
+ "section_id": "5A",
1346
+ "duration": 1
1347
+ },
1348
+ "RM&IPR-5B-0": {
1349
+ "start_slot": 28,
1350
+ "day_index": 3,
1351
+ "day_name": "Thursday",
1352
+ "period_index": 4,
1353
+ "room_id": "AI09 1F26",
1354
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1355
+ "faculty_name": "Basavaraj",
1356
+ "subject_code": "RM&IPR",
1357
+ "section_id": "5B",
1358
+ "duration": 1
1359
+ },
1360
+ "EVS-5A-0": {
1361
+ "start_slot": 5,
1362
+ "day_index": 0,
1363
+ "day_name": "Monday",
1364
+ "period_index": 5,
1365
+ "room_id": "AI09 3F 03",
1366
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1367
+ "faculty_name": "CIVIL_1",
1368
+ "subject_code": "EVS",
1369
+ "section_id": "5A",
1370
+ "duration": 1
1371
+ },
1372
+ "EVS-5A-1": {
1373
+ "start_slot": 36,
1374
+ "day_index": 4,
1375
+ "day_name": "Friday",
1376
+ "period_index": 4,
1377
+ "room_id": "AI09 3F 01",
1378
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1379
+ "faculty_name": "CIVIL_1",
1380
+ "subject_code": "EVS",
1381
+ "section_id": "5A",
1382
+ "duration": 1
1383
+ },
1384
+ "EVS-5B-0": {
1385
+ "start_slot": 22,
1386
+ "day_index": 2,
1387
+ "day_name": "Wednesday",
1388
+ "period_index": 6,
1389
+ "room_id": "AI09 1F26",
1390
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1391
+ "faculty_name": "CIVIL_1",
1392
+ "subject_code": "EVS",
1393
+ "section_id": "5B",
1394
+ "duration": 1
1395
+ },
1396
+ "EVS-5B-1": {
1397
+ "start_slot": 31,
1398
+ "day_index": 3,
1399
+ "day_name": "Thursday",
1400
+ "period_index": 7,
1401
+ "room_id": "AI09 1F26",
1402
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1403
+ "faculty_name": "CIVIL_1",
1404
+ "subject_code": "EVS",
1405
+ "section_id": "5B",
1406
+ "duration": 1
1407
+ },
1408
+ "SEPM-5DS-0": {
1409
+ "start_slot": 17,
1410
+ "day_index": 2,
1411
+ "day_name": "Wednesday",
1412
+ "period_index": 1,
1413
+ "room_id": "AI09 2F 13",
1414
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1415
+ "faculty_name": "Mr. Ranjan Kumar",
1416
+ "subject_code": "SEPM",
1417
+ "section_id": "5DS",
1418
+ "duration": 1
1419
+ },
1420
+ "SEPM-5DS-1": {
1421
+ "start_slot": 8,
1422
+ "day_index": 1,
1423
+ "day_name": "Tuesday",
1424
+ "period_index": 0,
1425
+ "room_id": "AI09 1F 25",
1426
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1427
+ "faculty_name": "Mr. Ranjan Kumar",
1428
+ "subject_code": "SEPM",
1429
+ "section_id": "5DS",
1430
+ "duration": 1
1431
+ },
1432
+ "SEPM-5DS-2": {
1433
+ "start_slot": 3,
1434
+ "day_index": 0,
1435
+ "day_name": "Monday",
1436
+ "period_index": 3,
1437
+ "room_id": "AI09 2F 13",
1438
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1439
+ "faculty_name": "Mr. Ranjan Kumar",
1440
+ "subject_code": "SEPM",
1441
+ "section_id": "5DS",
1442
+ "duration": 1
1443
+ },
1444
+ "SEPM-5DS-3": {
1445
+ "start_slot": 25,
1446
+ "day_index": 3,
1447
+ "day_name": "Thursday",
1448
+ "period_index": 1,
1449
+ "room_id": "AI09 2F 12",
1450
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1451
+ "faculty_name": "Mr. Ranjan Kumar",
1452
+ "subject_code": "SEPM",
1453
+ "section_id": "5DS",
1454
+ "duration": 1
1455
+ },
1456
+ "CN-5DS-0": {
1457
+ "start_slot": 16,
1458
+ "day_index": 2,
1459
+ "day_name": "Wednesday",
1460
+ "period_index": 0,
1461
+ "room_id": "AI09 2F 12",
1462
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1463
+ "faculty_name": "Mr. Nanda Kumar N",
1464
+ "subject_code": "CN",
1465
+ "section_id": "5DS",
1466
+ "duration": 1
1467
+ },
1468
+ "CN-5DS-1": {
1469
+ "start_slot": 2,
1470
+ "day_index": 0,
1471
+ "day_name": "Monday",
1472
+ "period_index": 2,
1473
+ "room_id": "AI09 2F 12",
1474
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1475
+ "faculty_name": "Mr. Nanda Kumar N",
1476
+ "subject_code": "CN",
1477
+ "section_id": "5DS",
1478
+ "duration": 1
1479
+ },
1480
+ "CN-5DS-2": {
1481
+ "start_slot": 34,
1482
+ "day_index": 4,
1483
+ "day_name": "Friday",
1484
+ "period_index": 2,
1485
+ "room_id": "AI09 3F 03",
1486
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1487
+ "faculty_name": "Mr. Nanda Kumar N",
1488
+ "subject_code": "CN",
1489
+ "section_id": "5DS",
1490
+ "duration": 1
1491
+ },
1492
+ "CN Lab-5DS-B1-BLOCK": {
1493
+ "start_slot": 22,
1494
+ "day_index": 2,
1495
+ "day_name": "Wednesday",
1496
+ "period_index": 6,
1497
+ "room_id": "AI09 2F 12",
1498
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1499
+ "faculty_name": "Mr. Nanda Kumar N",
1500
+ "subject_code": "CN Lab",
1501
+ "section_id": "5DS-B1",
1502
+ "duration": 2
1503
+ },
1504
+ "CN Lab-5DS-B2-BLOCK": {
1505
+ "start_slot": 38,
1506
+ "day_index": 4,
1507
+ "day_name": "Friday",
1508
+ "period_index": 6,
1509
+ "room_id": "AI09 1F 25",
1510
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1511
+ "faculty_name": "Mr. Nanda Kumar N",
1512
+ "subject_code": "CN Lab",
1513
+ "section_id": "5DS-B2",
1514
+ "duration": 2
1515
+ },
1516
+ "TOC-5DS-0": {
1517
+ "start_slot": 24,
1518
+ "day_index": 3,
1519
+ "day_name": "Thursday",
1520
+ "period_index": 0,
1521
+ "room_id": "AI09 1F26",
1522
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1523
+ "faculty_name": "Ms. Vinutha M",
1524
+ "subject_code": "TOC",
1525
+ "section_id": "5DS",
1526
+ "duration": 1
1527
+ },
1528
+ "TOC-5DS-1": {
1529
+ "start_slot": 19,
1530
+ "day_index": 2,
1531
+ "day_name": "Wednesday",
1532
+ "period_index": 3,
1533
+ "room_id": "AI09 3F 03",
1534
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1535
+ "faculty_name": "Ms. Vinutha M",
1536
+ "subject_code": "TOC",
1537
+ "section_id": "5DS",
1538
+ "duration": 1
1539
+ },
1540
+ "TOC-5DS-2": {
1541
+ "start_slot": 9,
1542
+ "day_index": 1,
1543
+ "day_name": "Tuesday",
1544
+ "period_index": 1,
1545
+ "room_id": "AI09 1F26",
1546
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1547
+ "faculty_name": "Ms. Vinutha M",
1548
+ "subject_code": "TOC",
1549
+ "section_id": "5DS",
1550
+ "duration": 1
1551
+ },
1552
+ "TOC-5DS-3": {
1553
+ "start_slot": 0,
1554
+ "day_index": 0,
1555
+ "day_name": "Monday",
1556
+ "period_index": 0,
1557
+ "room_id": "AI09 2F 14",
1558
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1559
+ "faculty_name": "Ms. Vinutha M",
1560
+ "subject_code": "TOC",
1561
+ "section_id": "5DS",
1562
+ "duration": 1
1563
+ },
1564
+ "TOC-5DS-4": {
1565
+ "start_slot": 35,
1566
+ "day_index": 4,
1567
+ "day_name": "Friday",
1568
+ "period_index": 3,
1569
+ "room_id": "AI09 2F 08",
1570
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1571
+ "faculty_name": "Ms. Vinutha M",
1572
+ "subject_code": "TOC",
1573
+ "section_id": "5DS",
1574
+ "duration": 1
1575
+ },
1576
+ "DV Lab-5DS-B1-BLOCK": {
1577
+ "start_slot": 20,
1578
+ "day_index": 2,
1579
+ "day_name": "Wednesday",
1580
+ "period_index": 4,
1581
+ "room_id": "AI03 2F 01",
1582
+ "room_name": "AI03 2F 01 (ASD BLOCK)",
1583
+ "faculty_name": "Syed Musadiq Illahi / Chalasani Jayanth",
1584
+ "subject_code": "DV Lab",
1585
+ "section_id": "5DS-B1",
1586
+ "duration": 2
1587
+ },
1588
+ "DV Lab-5DS-B2-BLOCK": {
1589
+ "start_slot": 26,
1590
+ "day_index": 3,
1591
+ "day_name": "Thursday",
1592
+ "period_index": 2,
1593
+ "room_id": "AI03 2F 02",
1594
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
1595
+ "faculty_name": "Praveen Arokia Raj / Chalasani Jayanth",
1596
+ "subject_code": "DV Lab",
1597
+ "section_id": "5DS-B2",
1598
+ "duration": 2
1599
+ },
1600
+ "NoSQL DB-5DS-0": {
1601
+ "start_slot": 13,
1602
+ "day_index": 1,
1603
+ "day_name": "Tuesday",
1604
+ "period_index": 5,
1605
+ "room_id": "AI09 3F 03",
1606
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1607
+ "faculty_name": "Mr. Mhd Tahir Mirji",
1608
+ "subject_code": "NoSQL DB",
1609
+ "section_id": "5DS",
1610
+ "duration": 1
1611
+ },
1612
+ "NoSQL DB-5DS-1": {
1613
+ "start_slot": 4,
1614
+ "day_index": 0,
1615
+ "day_name": "Monday",
1616
+ "period_index": 4,
1617
+ "room_id": "AI09 2F 12",
1618
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1619
+ "faculty_name": "Mr. Mhd Tahir Mirji",
1620
+ "subject_code": "NoSQL DB",
1621
+ "section_id": "5DS",
1622
+ "duration": 1
1623
+ },
1624
+ "NoSQL DB-5DS-2": {
1625
+ "start_slot": 36,
1626
+ "day_index": 4,
1627
+ "day_name": "Friday",
1628
+ "period_index": 4,
1629
+ "room_id": "AI09 1F26",
1630
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1631
+ "faculty_name": "Mr. Mhd Tahir Mirji",
1632
+ "subject_code": "NoSQL DB",
1633
+ "section_id": "5DS",
1634
+ "duration": 1
1635
+ },
1636
+ "DW-5DS-0": {
1637
+ "start_slot": 13,
1638
+ "day_index": 1,
1639
+ "day_name": "Tuesday",
1640
+ "period_index": 5,
1641
+ "room_id": "AI09 1F 25",
1642
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1643
+ "faculty_name": "Mr. Abhijith S",
1644
+ "subject_code": "DW",
1645
+ "section_id": "5DS",
1646
+ "duration": 1
1647
+ },
1648
+ "DW-5DS-1": {
1649
+ "start_slot": 4,
1650
+ "day_index": 0,
1651
+ "day_name": "Monday",
1652
+ "period_index": 4,
1653
+ "room_id": "AI09 3F 01",
1654
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1655
+ "faculty_name": "Mr. Abhijith S",
1656
+ "subject_code": "DW",
1657
+ "section_id": "5DS",
1658
+ "duration": 1
1659
+ },
1660
+ "DW-5DS-2": {
1661
+ "start_slot": 36,
1662
+ "day_index": 4,
1663
+ "day_name": "Friday",
1664
+ "period_index": 4,
1665
+ "room_id": "AI09 3F 03",
1666
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1667
+ "faculty_name": "Mr. Abhijith S",
1668
+ "subject_code": "DW",
1669
+ "section_id": "5DS",
1670
+ "duration": 1
1671
+ },
1672
+ "RM&IPR-5DS-0": {
1673
+ "start_slot": 29,
1674
+ "day_index": 3,
1675
+ "day_name": "Thursday",
1676
+ "period_index": 5,
1677
+ "room_id": "AI09 1F26",
1678
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1679
+ "faculty_name": "Basavaraj",
1680
+ "subject_code": "RM&IPR",
1681
+ "section_id": "5DS",
1682
+ "duration": 1
1683
+ },
1684
+ "EVS-5DS-0": {
1685
+ "start_slot": 7,
1686
+ "day_index": 0,
1687
+ "day_name": "Monday",
1688
+ "period_index": 7,
1689
+ "room_id": "AI09 2F 14",
1690
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1691
+ "faculty_name": "CIVIL_1",
1692
+ "subject_code": "EVS",
1693
+ "section_id": "5DS",
1694
+ "duration": 1
1695
+ },
1696
+ "EVS-5DS-1": {
1697
+ "start_slot": 30,
1698
+ "day_index": 3,
1699
+ "day_name": "Thursday",
1700
+ "period_index": 6,
1701
+ "room_id": "AI09 1F26",
1702
+ "room_name": "AI09 1F26 (ECE BLOCK)",
1703
+ "faculty_name": "CIVIL_1",
1704
+ "subject_code": "EVS",
1705
+ "section_id": "5DS",
1706
+ "duration": 1
1707
+ },
1708
+ "Maths (Prob & Stats)-3A-0": {
1709
+ "start_slot": 25,
1710
+ "day_index": 3,
1711
+ "day_name": "Thursday",
1712
+ "period_index": 1,
1713
+ "room_id": "AI09 3F 01",
1714
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1715
+ "faculty_name": "Maths Department",
1716
+ "subject_code": "Maths (Prob & Stats)",
1717
+ "section_id": "3A",
1718
+ "duration": 1
1719
+ },
1720
+ "Maths (Prob & Stats)-3A-1": {
1721
+ "start_slot": 16,
1722
+ "day_index": 2,
1723
+ "day_name": "Wednesday",
1724
+ "period_index": 0,
1725
+ "room_id": "AI09 2F 13",
1726
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1727
+ "faculty_name": "Maths Department",
1728
+ "subject_code": "Maths (Prob & Stats)",
1729
+ "section_id": "3A",
1730
+ "duration": 1
1731
+ },
1732
+ "Maths (Prob & Stats)-3A-2": {
1733
+ "start_slot": 1,
1734
+ "day_index": 0,
1735
+ "day_name": "Monday",
1736
+ "period_index": 1,
1737
+ "room_id": "AI09 2F 12",
1738
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1739
+ "faculty_name": "Maths Department",
1740
+ "subject_code": "Maths (Prob & Stats)",
1741
+ "section_id": "3A",
1742
+ "duration": 1
1743
+ },
1744
+ "Maths (Prob & Stats)-3A-3": {
1745
+ "start_slot": 8,
1746
+ "day_index": 1,
1747
+ "day_name": "Tuesday",
1748
+ "period_index": 0,
1749
+ "room_id": "AI09 2F 14",
1750
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1751
+ "faculty_name": "Maths Department",
1752
+ "subject_code": "Maths (Prob & Stats)",
1753
+ "section_id": "3A",
1754
+ "duration": 1
1755
+ },
1756
+ "Maths (Prob & Stats)-3A-4": {
1757
+ "start_slot": 34,
1758
+ "day_index": 4,
1759
+ "day_name": "Friday",
1760
+ "period_index": 2,
1761
+ "room_id": "AI09 2F 14",
1762
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1763
+ "faculty_name": "Maths Department",
1764
+ "subject_code": "Maths (Prob & Stats)",
1765
+ "section_id": "3A",
1766
+ "duration": 1
1767
+ },
1768
+ "Maths (Prob & Stats)-3B-0": {
1769
+ "start_slot": 26,
1770
+ "day_index": 3,
1771
+ "day_name": "Thursday",
1772
+ "period_index": 2,
1773
+ "room_id": "AI09 2F 13",
1774
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1775
+ "faculty_name": "Maths Department",
1776
+ "subject_code": "Maths (Prob & Stats)",
1777
+ "section_id": "3B",
1778
+ "duration": 1
1779
+ },
1780
+ "Maths (Prob & Stats)-3B-1": {
1781
+ "start_slot": 19,
1782
+ "day_index": 2,
1783
+ "day_name": "Wednesday",
1784
+ "period_index": 3,
1785
+ "room_id": "AI09 2F 14",
1786
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
1787
+ "faculty_name": "Maths Department",
1788
+ "subject_code": "Maths (Prob & Stats)",
1789
+ "section_id": "3B",
1790
+ "duration": 1
1791
+ },
1792
+ "Maths (Prob & Stats)-3B-2": {
1793
+ "start_slot": 9,
1794
+ "day_index": 1,
1795
+ "day_name": "Tuesday",
1796
+ "period_index": 1,
1797
+ "room_id": "AI09 3F 03",
1798
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1799
+ "faculty_name": "Maths Department",
1800
+ "subject_code": "Maths (Prob & Stats)",
1801
+ "section_id": "3B",
1802
+ "duration": 1
1803
+ },
1804
+ "Maths (Prob & Stats)-3B-3": {
1805
+ "start_slot": 2,
1806
+ "day_index": 0,
1807
+ "day_name": "Monday",
1808
+ "period_index": 2,
1809
+ "room_id": "AI09 2F 08",
1810
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1811
+ "faculty_name": "Maths Department",
1812
+ "subject_code": "Maths (Prob & Stats)",
1813
+ "section_id": "3B",
1814
+ "duration": 1
1815
+ },
1816
+ "Maths (Prob & Stats)-3B-4": {
1817
+ "start_slot": 35,
1818
+ "day_index": 4,
1819
+ "day_name": "Friday",
1820
+ "period_index": 3,
1821
+ "room_id": "AI09 2F 12",
1822
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
1823
+ "faculty_name": "Maths Department",
1824
+ "subject_code": "Maths (Prob & Stats)",
1825
+ "section_id": "3B",
1826
+ "duration": 1
1827
+ },
1828
+ "DDCO-3A-0": {
1829
+ "start_slot": 2,
1830
+ "day_index": 0,
1831
+ "day_name": "Monday",
1832
+ "period_index": 2,
1833
+ "room_id": "AI09 2F 13",
1834
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1835
+ "faculty_name": "Mr. Ranjan Kumar",
1836
+ "subject_code": "DDCO",
1837
+ "section_id": "3A",
1838
+ "duration": 1
1839
+ },
1840
+ "DDCO-3A-1": {
1841
+ "start_slot": 19,
1842
+ "day_index": 2,
1843
+ "day_name": "Wednesday",
1844
+ "period_index": 3,
1845
+ "room_id": "AI09 3F 08",
1846
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
1847
+ "faculty_name": "Mr. Ranjan Kumar",
1848
+ "subject_code": "DDCO",
1849
+ "section_id": "3A",
1850
+ "duration": 1
1851
+ },
1852
+ "DDCO-3A-2": {
1853
+ "start_slot": 24,
1854
+ "day_index": 3,
1855
+ "day_name": "Thursday",
1856
+ "period_index": 0,
1857
+ "room_id": "AI09 3F 01",
1858
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
1859
+ "faculty_name": "Mr. Ranjan Kumar",
1860
+ "subject_code": "DDCO",
1861
+ "section_id": "3A",
1862
+ "duration": 1
1863
+ },
1864
+ "DDCO-3A-4": {
1865
+ "start_slot": 33,
1866
+ "day_index": 4,
1867
+ "day_name": "Friday",
1868
+ "period_index": 1,
1869
+ "room_id": "AI09 3F 03",
1870
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1871
+ "faculty_name": "Mr. Ranjan Kumar",
1872
+ "subject_code": "DDCO",
1873
+ "section_id": "3A",
1874
+ "duration": 1
1875
+ },
1876
+ "DDCO-3B-0": {
1877
+ "start_slot": 27,
1878
+ "day_index": 3,
1879
+ "day_name": "Thursday",
1880
+ "period_index": 3,
1881
+ "room_id": "AI09 3F 08",
1882
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
1883
+ "faculty_name": "Mr. Ranjan Kumar",
1884
+ "subject_code": "DDCO",
1885
+ "section_id": "3B",
1886
+ "duration": 1
1887
+ },
1888
+ "DDCO-3B-1": {
1889
+ "start_slot": 18,
1890
+ "day_index": 2,
1891
+ "day_name": "Wednesday",
1892
+ "period_index": 2,
1893
+ "room_id": "AI09 2F 08",
1894
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1895
+ "faculty_name": "Mr. Ranjan Kumar",
1896
+ "subject_code": "DDCO",
1897
+ "section_id": "3B",
1898
+ "duration": 1
1899
+ },
1900
+ "DDCO-3B-2": {
1901
+ "start_slot": 10,
1902
+ "day_index": 1,
1903
+ "day_name": "Tuesday",
1904
+ "period_index": 2,
1905
+ "room_id": "AI09 3F 03",
1906
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1907
+ "faculty_name": "Mr. Ranjan Kumar",
1908
+ "subject_code": "DDCO",
1909
+ "section_id": "3B",
1910
+ "duration": 1
1911
+ },
1912
+ "DDCO-3B-3": {
1913
+ "start_slot": 0,
1914
+ "day_index": 0,
1915
+ "day_name": "Monday",
1916
+ "period_index": 0,
1917
+ "room_id": "AI09 3F 03",
1918
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1919
+ "faculty_name": "Mr. Ranjan Kumar",
1920
+ "subject_code": "DDCO",
1921
+ "section_id": "3B",
1922
+ "duration": 1
1923
+ },
1924
+ "DDCO-3B-4": {
1925
+ "start_slot": 34,
1926
+ "day_index": 4,
1927
+ "day_name": "Friday",
1928
+ "period_index": 2,
1929
+ "room_id": "AI09 1F 25",
1930
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1931
+ "faculty_name": "Mr. Ranjan Kumar",
1932
+ "subject_code": "DDCO",
1933
+ "section_id": "3B",
1934
+ "duration": 1
1935
+ },
1936
+ "OS-3A-0": {
1937
+ "start_slot": 18,
1938
+ "day_index": 2,
1939
+ "day_name": "Wednesday",
1940
+ "period_index": 2,
1941
+ "room_id": "AI09 2F 13",
1942
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1943
+ "faculty_name": "Mr. Sanjay P",
1944
+ "subject_code": "OS",
1945
+ "section_id": "3A",
1946
+ "duration": 1
1947
+ },
1948
+ "OS-3A-1": {
1949
+ "start_slot": 0,
1950
+ "day_index": 0,
1951
+ "day_name": "Monday",
1952
+ "period_index": 0,
1953
+ "room_id": "AI09 2F 13",
1954
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
1955
+ "faculty_name": "Mr. Sanjay P",
1956
+ "subject_code": "OS",
1957
+ "section_id": "3A",
1958
+ "duration": 1
1959
+ },
1960
+ "OS-3A-2": {
1961
+ "start_slot": 32,
1962
+ "day_index": 4,
1963
+ "day_name": "Friday",
1964
+ "period_index": 0,
1965
+ "room_id": "AI09 3F 03",
1966
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
1967
+ "faculty_name": "Mr. Sanjay P",
1968
+ "subject_code": "OS",
1969
+ "section_id": "3A",
1970
+ "duration": 1
1971
+ },
1972
+ "OS-3B-0": {
1973
+ "start_slot": 16,
1974
+ "day_index": 2,
1975
+ "day_name": "Wednesday",
1976
+ "period_index": 0,
1977
+ "room_id": "AI09 2F 08",
1978
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
1979
+ "faculty_name": "Ms. Vinutha M",
1980
+ "subject_code": "OS",
1981
+ "section_id": "3B",
1982
+ "duration": 1
1983
+ },
1984
+ "OS-3B-1": {
1985
+ "start_slot": 1,
1986
+ "day_index": 0,
1987
+ "day_name": "Monday",
1988
+ "period_index": 1,
1989
+ "room_id": "AI09 1F 25",
1990
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
1991
+ "faculty_name": "Ms. Vinutha M",
1992
+ "subject_code": "OS",
1993
+ "section_id": "3B",
1994
+ "duration": 1
1995
+ },
1996
+ "OS-3B-2": {
1997
+ "start_slot": 33,
1998
+ "day_index": 4,
1999
+ "day_name": "Friday",
2000
+ "period_index": 1,
2001
+ "room_id": "AI09 2F 13",
2002
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
2003
+ "faculty_name": "Ms. Vinutha M",
2004
+ "subject_code": "OS",
2005
+ "section_id": "3B",
2006
+ "duration": 1
2007
+ },
2008
+ "DSA-3A-0": {
2009
+ "start_slot": 17,
2010
+ "day_index": 2,
2011
+ "day_name": "Wednesday",
2012
+ "period_index": 1,
2013
+ "room_id": "AI09 3F 08",
2014
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
2015
+ "faculty_name": "Ms. Surbhi",
2016
+ "subject_code": "DSA",
2017
+ "section_id": "3A",
2018
+ "duration": 1
2019
+ },
2020
+ "DSA-3A-1": {
2021
+ "start_slot": 3,
2022
+ "day_index": 0,
2023
+ "day_name": "Monday",
2024
+ "period_index": 3,
2025
+ "room_id": "AI09 2F 08",
2026
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2027
+ "faculty_name": "Ms. Surbhi",
2028
+ "subject_code": "DSA",
2029
+ "section_id": "3A",
2030
+ "duration": 1
2031
+ },
2032
+ "DSA-3A-2": {
2033
+ "start_slot": 35,
2034
+ "day_index": 4,
2035
+ "day_name": "Friday",
2036
+ "period_index": 3,
2037
+ "room_id": "AI09 3F 01",
2038
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
2039
+ "faculty_name": "Ms. Surbhi",
2040
+ "subject_code": "DSA",
2041
+ "section_id": "3A",
2042
+ "duration": 1
2043
+ },
2044
+ "DSA-3B-0": {
2045
+ "start_slot": 3,
2046
+ "day_index": 0,
2047
+ "day_name": "Monday",
2048
+ "period_index": 3,
2049
+ "room_id": "AI09 3F 03",
2050
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2051
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2052
+ "subject_code": "DSA",
2053
+ "section_id": "3B",
2054
+ "duration": 1
2055
+ },
2056
+ "DSA-3B-1": {
2057
+ "start_slot": 11,
2058
+ "day_index": 1,
2059
+ "day_name": "Tuesday",
2060
+ "period_index": 3,
2061
+ "room_id": "AI09 2F 13",
2062
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
2063
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2064
+ "subject_code": "DSA",
2065
+ "section_id": "3B",
2066
+ "duration": 1
2067
+ },
2068
+ "DSA-3B-2": {
2069
+ "start_slot": 17,
2070
+ "day_index": 2,
2071
+ "day_name": "Wednesday",
2072
+ "period_index": 1,
2073
+ "room_id": "AI09 1F26",
2074
+ "room_name": "AI09 1F26 (ECE BLOCK)",
2075
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2076
+ "subject_code": "DSA",
2077
+ "section_id": "3B",
2078
+ "duration": 1
2079
+ },
2080
+ "DSA Lab-3A-B1-BLOCK": {
2081
+ "start_slot": 10,
2082
+ "day_index": 1,
2083
+ "day_name": "Tuesday",
2084
+ "period_index": 2,
2085
+ "room_id": "AI03 2F 02",
2086
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2087
+ "faculty_name": "Surbhi / Nanda Kumar N",
2088
+ "subject_code": "DSA Lab",
2089
+ "section_id": "3A-B1",
2090
+ "duration": 2
2091
+ },
2092
+ "DSA Lab-3A-B2-BLOCK": {
2093
+ "start_slot": 20,
2094
+ "day_index": 2,
2095
+ "day_name": "Wednesday",
2096
+ "period_index": 4,
2097
+ "room_id": "AI03 2F 02",
2098
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2099
+ "faculty_name": "Nanda Kumar N / Surbhi",
2100
+ "subject_code": "DSA Lab",
2101
+ "section_id": "3A-B2",
2102
+ "duration": 2
2103
+ },
2104
+ "DSA Lab-3B-B1-BLOCK": {
2105
+ "start_slot": 24,
2106
+ "day_index": 3,
2107
+ "day_name": "Thursday",
2108
+ "period_index": 0,
2109
+ "room_id": "AI03 2F 02",
2110
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2111
+ "faculty_name": "Surbhi / Mhd Tahir Mirji",
2112
+ "subject_code": "DSA Lab",
2113
+ "section_id": "3B-B1",
2114
+ "duration": 2
2115
+ },
2116
+ "OOP with Java-3A-0": {
2117
+ "start_slot": 27,
2118
+ "day_index": 3,
2119
+ "day_name": "Thursday",
2120
+ "period_index": 3,
2121
+ "room_id": "AI09 3F 03",
2122
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2123
+ "faculty_name": "Nandita Tarikeri",
2124
+ "subject_code": "OOP with Java",
2125
+ "section_id": "3A",
2126
+ "duration": 1
2127
+ },
2128
+ "OOP with Java-3B-0": {
2129
+ "start_slot": 32,
2130
+ "day_index": 4,
2131
+ "day_name": "Friday",
2132
+ "period_index": 0,
2133
+ "room_id": "AI09 1F26",
2134
+ "room_name": "AI09 1F26 (ECE BLOCK)",
2135
+ "faculty_name": "Nandita Tarikeri",
2136
+ "subject_code": "OOP with Java",
2137
+ "section_id": "3B",
2138
+ "duration": 1
2139
+ },
2140
+ "EDA Lab-3A-BLOCK": {
2141
+ "start_slot": 12,
2142
+ "day_index": 1,
2143
+ "day_name": "Tuesday",
2144
+ "period_index": 4,
2145
+ "room_id": "AI09 3F 08",
2146
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
2147
+ "faculty_name": "Dr. Kavitha Nair R",
2148
+ "subject_code": "EDA Lab",
2149
+ "section_id": "3A",
2150
+ "duration": 2
2151
+ },
2152
+ "EDA Lab-3B-BLOCK": {
2153
+ "start_slot": 12,
2154
+ "day_index": 1,
2155
+ "day_name": "Tuesday",
2156
+ "period_index": 4,
2157
+ "room_id": "AI09 2F 12",
2158
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2159
+ "faculty_name": "Dr. Kavitha Nair R",
2160
+ "subject_code": "EDA Lab",
2161
+ "section_id": "3B",
2162
+ "duration": 2
2163
+ },
2164
+ "PM with Git-3A-BLOCK": {
2165
+ "start_slot": 12,
2166
+ "day_index": 1,
2167
+ "day_name": "Tuesday",
2168
+ "period_index": 4,
2169
+ "room_id": "AI09 2F 14",
2170
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
2171
+ "faculty_name": "Mr. Sanjay P",
2172
+ "subject_code": "PM with Git",
2173
+ "section_id": "3A",
2174
+ "duration": 2
2175
+ },
2176
+ "PM with Git-3B-BLOCK": {
2177
+ "start_slot": 12,
2178
+ "day_index": 1,
2179
+ "day_name": "Tuesday",
2180
+ "period_index": 4,
2181
+ "room_id": "AI09 2F 08",
2182
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2183
+ "faculty_name": "Mr. Sanjay P",
2184
+ "subject_code": "PM with Git",
2185
+ "section_id": "3B",
2186
+ "duration": 2
2187
+ },
2188
+ "Maths LE-3A-0": {
2189
+ "start_slot": 36,
2190
+ "day_index": 4,
2191
+ "day_name": "Friday",
2192
+ "period_index": 4,
2193
+ "room_id": "AI09 3F 08",
2194
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
2195
+ "faculty_name": "Maths Department",
2196
+ "subject_code": "Maths LE",
2197
+ "section_id": "3A",
2198
+ "duration": 1
2199
+ },
2200
+ "Maths LE-3B-0": {
2201
+ "start_slot": 21,
2202
+ "day_index": 2,
2203
+ "day_name": "Wednesday",
2204
+ "period_index": 5,
2205
+ "room_id": "AI09 1F26",
2206
+ "room_name": "AI09 1F26 (ECE BLOCK)",
2207
+ "faculty_name": "Maths Department",
2208
+ "subject_code": "Maths LE",
2209
+ "section_id": "3B",
2210
+ "duration": 1
2211
+ },
2212
+ "Maths (Prob & Stats)-3DS-0": {
2213
+ "start_slot": 32,
2214
+ "day_index": 4,
2215
+ "day_name": "Friday",
2216
+ "period_index": 0,
2217
+ "room_id": "AI09 2F 12",
2218
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2219
+ "faculty_name": "Maths Department",
2220
+ "subject_code": "Maths (Prob & Stats)",
2221
+ "section_id": "3DS",
2222
+ "duration": 1
2223
+ },
2224
+ "Maths (Prob & Stats)-3DS-1": {
2225
+ "start_slot": 18,
2226
+ "day_index": 2,
2227
+ "day_name": "Wednesday",
2228
+ "period_index": 2,
2229
+ "room_id": "AI09 3F 01",
2230
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
2231
+ "faculty_name": "Maths Department",
2232
+ "subject_code": "Maths (Prob & Stats)",
2233
+ "section_id": "3DS",
2234
+ "duration": 1
2235
+ },
2236
+ "Maths (Prob & Stats)-3DS-2": {
2237
+ "start_slot": 11,
2238
+ "day_index": 1,
2239
+ "day_name": "Tuesday",
2240
+ "period_index": 3,
2241
+ "room_id": "AI09 2F 12",
2242
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2243
+ "faculty_name": "Maths Department",
2244
+ "subject_code": "Maths (Prob & Stats)",
2245
+ "section_id": "3DS",
2246
+ "duration": 1
2247
+ },
2248
+ "Maths (Prob & Stats)-3DS-3": {
2249
+ "start_slot": 0,
2250
+ "day_index": 0,
2251
+ "day_name": "Monday",
2252
+ "period_index": 0,
2253
+ "room_id": "AI09 3F 01",
2254
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
2255
+ "faculty_name": "Maths Department",
2256
+ "subject_code": "Maths (Prob & Stats)",
2257
+ "section_id": "3DS",
2258
+ "duration": 1
2259
+ },
2260
+ "Maths (Prob & Stats)-3DS-4": {
2261
+ "start_slot": 27,
2262
+ "day_index": 3,
2263
+ "day_name": "Thursday",
2264
+ "period_index": 3,
2265
+ "room_id": "AI09 3F 01",
2266
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
2267
+ "faculty_name": "Maths Department",
2268
+ "subject_code": "Maths (Prob & Stats)",
2269
+ "section_id": "3DS",
2270
+ "duration": 1
2271
+ },
2272
+ "DDCO-3DS-0": {
2273
+ "start_slot": 24,
2274
+ "day_index": 3,
2275
+ "day_name": "Thursday",
2276
+ "period_index": 0,
2277
+ "room_id": "AI09 3F 03",
2278
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2279
+ "faculty_name": "Mr. Chalasani Jayanth",
2280
+ "subject_code": "DDCO",
2281
+ "section_id": "3DS",
2282
+ "duration": 1
2283
+ },
2284
+ "DDCO-3DS-1": {
2285
+ "start_slot": 8,
2286
+ "day_index": 1,
2287
+ "day_name": "Tuesday",
2288
+ "period_index": 0,
2289
+ "room_id": "AI09 2F 08",
2290
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2291
+ "faculty_name": "Mr. Chalasani Jayanth",
2292
+ "subject_code": "DDCO",
2293
+ "section_id": "3DS",
2294
+ "duration": 1
2295
+ },
2296
+ "DDCO-3DS-2": {
2297
+ "start_slot": 17,
2298
+ "day_index": 2,
2299
+ "day_name": "Wednesday",
2300
+ "period_index": 1,
2301
+ "room_id": "AI09 2F 12",
2302
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2303
+ "faculty_name": "Mr. Chalasani Jayanth",
2304
+ "subject_code": "DDCO",
2305
+ "section_id": "3DS",
2306
+ "duration": 1
2307
+ },
2308
+ "DDCO-3DS-3": {
2309
+ "start_slot": 2,
2310
+ "day_index": 0,
2311
+ "day_name": "Monday",
2312
+ "period_index": 2,
2313
+ "room_id": "AI09 2F 14",
2314
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
2315
+ "faculty_name": "Mr. Chalasani Jayanth",
2316
+ "subject_code": "DDCO",
2317
+ "section_id": "3DS",
2318
+ "duration": 1
2319
+ },
2320
+ "DDCO-3DS-4": {
2321
+ "start_slot": 33,
2322
+ "day_index": 4,
2323
+ "day_name": "Friday",
2324
+ "period_index": 1,
2325
+ "room_id": "AI09 2F 08",
2326
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2327
+ "faculty_name": "Mr. Chalasani Jayanth",
2328
+ "subject_code": "DDCO",
2329
+ "section_id": "3DS",
2330
+ "duration": 1
2331
+ },
2332
+ "OS-3DS-0": {
2333
+ "start_slot": 10,
2334
+ "day_index": 1,
2335
+ "day_name": "Tuesday",
2336
+ "period_index": 2,
2337
+ "room_id": "AI09 2F 13",
2338
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
2339
+ "faculty_name": "Mr. Sanjay P",
2340
+ "subject_code": "OS",
2341
+ "section_id": "3DS",
2342
+ "duration": 1
2343
+ },
2344
+ "OS-3DS-1": {
2345
+ "start_slot": 3,
2346
+ "day_index": 0,
2347
+ "day_name": "Monday",
2348
+ "period_index": 3,
2349
+ "room_id": "AI09 1F 25",
2350
+ "room_name": "AI09 1F 25 (ECE BLOCK)",
2351
+ "faculty_name": "Mr. Sanjay P",
2352
+ "subject_code": "OS",
2353
+ "section_id": "3DS",
2354
+ "duration": 1
2355
+ },
2356
+ "OS-3DS-2": {
2357
+ "start_slot": 34,
2358
+ "day_index": 4,
2359
+ "day_name": "Friday",
2360
+ "period_index": 2,
2361
+ "room_id": "AI09 2F 12",
2362
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2363
+ "faculty_name": "Mr. Sanjay P",
2364
+ "subject_code": "OS",
2365
+ "section_id": "3DS",
2366
+ "duration": 1
2367
+ },
2368
+ "DSA-3DS-0": {
2369
+ "start_slot": 16,
2370
+ "day_index": 2,
2371
+ "day_name": "Wednesday",
2372
+ "period_index": 0,
2373
+ "room_id": "AI09 3F 03",
2374
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2375
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2376
+ "subject_code": "DSA",
2377
+ "section_id": "3DS",
2378
+ "duration": 1
2379
+ },
2380
+ "DSA-3DS-1": {
2381
+ "start_slot": 1,
2382
+ "day_index": 0,
2383
+ "day_name": "Monday",
2384
+ "period_index": 1,
2385
+ "room_id": "AI09 3F 03",
2386
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2387
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2388
+ "subject_code": "DSA",
2389
+ "section_id": "3DS",
2390
+ "duration": 1
2391
+ },
2392
+ "DSA-3DS-2": {
2393
+ "start_slot": 35,
2394
+ "day_index": 4,
2395
+ "day_name": "Friday",
2396
+ "period_index": 3,
2397
+ "room_id": "AI09 3F 08",
2398
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
2399
+ "faculty_name": "Mr. Mhd Tahir Mirji",
2400
+ "subject_code": "DSA",
2401
+ "section_id": "3DS",
2402
+ "duration": 1
2403
+ },
2404
+ "DSA Lab-3DS-B1-BLOCK": {
2405
+ "start_slot": 6,
2406
+ "day_index": 0,
2407
+ "day_name": "Monday",
2408
+ "period_index": 6,
2409
+ "room_id": "AI03 2F 01",
2410
+ "room_name": "AI03 2F 01 (ASD BLOCK)",
2411
+ "faculty_name": "Nanda Kumar N / Sanjay P",
2412
+ "subject_code": "DSA Lab",
2413
+ "section_id": "3DS-B1",
2414
+ "duration": 2
2415
+ },
2416
+ "OOP with Java-3DS-0": {
2417
+ "start_slot": 25,
2418
+ "day_index": 3,
2419
+ "day_name": "Thursday",
2420
+ "period_index": 1,
2421
+ "room_id": "AI09 2F 08",
2422
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2423
+ "faculty_name": "Nandita Tarikeri",
2424
+ "subject_code": "OOP with Java",
2425
+ "section_id": "3DS",
2426
+ "duration": 1
2427
+ },
2428
+ "EDA Lab-3DS-BLOCK": {
2429
+ "start_slot": 12,
2430
+ "day_index": 1,
2431
+ "day_name": "Tuesday",
2432
+ "period_index": 4,
2433
+ "room_id": "AI09 3F 01",
2434
+ "room_name": "AI09 3F 01 (ECE BLOCK)",
2435
+ "faculty_name": "Dr. Kavitha Nair R",
2436
+ "subject_code": "EDA Lab",
2437
+ "section_id": "3DS",
2438
+ "duration": 2
2439
+ },
2440
+ "Maths LE-3DS-0": {
2441
+ "start_slot": 31,
2442
+ "day_index": 3,
2443
+ "day_name": "Thursday",
2444
+ "period_index": 7,
2445
+ "room_id": "AI09 2F 08",
2446
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2447
+ "faculty_name": "Maths Department",
2448
+ "subject_code": "Maths LE",
2449
+ "section_id": "3DS",
2450
+ "duration": 1
2451
+ },
2452
+ "DV Lab-5A-B1-BLOCK": {
2453
+ "start_slot": 38,
2454
+ "day_index": 4,
2455
+ "day_name": "Friday",
2456
+ "period_index": 6,
2457
+ "room_id": "AI03 2F 02",
2458
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2459
+ "faculty_name": "S Anu Pallavi / Jovin Deglus",
2460
+ "subject_code": "DV Lab",
2461
+ "section_id": "5A-B1",
2462
+ "duration": 2
2463
+ },
2464
+ "DV Lab-5A-B2-BLOCK": {
2465
+ "start_slot": 29,
2466
+ "day_index": 3,
2467
+ "day_name": "Thursday",
2468
+ "period_index": 5,
2469
+ "room_id": "AI03 2F 01",
2470
+ "room_name": "AI03 2F 01 (ASD BLOCK)",
2471
+ "faculty_name": "Abhijith S / Chalasani Jayanth",
2472
+ "subject_code": "DV Lab",
2473
+ "section_id": "5A-B2",
2474
+ "duration": 2
2475
+ },
2476
+ "DSA Lab-3B-B2-BLOCK": {
2477
+ "start_slot": 29,
2478
+ "day_index": 3,
2479
+ "day_name": "Thursday",
2480
+ "period_index": 5,
2481
+ "room_id": "AI03 2F 02",
2482
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2483
+ "faculty_name": "Mhd Tahir Mirji / Surbhi",
2484
+ "subject_code": "DSA Lab",
2485
+ "section_id": "3B-B2",
2486
+ "duration": 2
2487
+ },
2488
+ "DSA Lab-3DS-B2-BLOCK": {
2489
+ "start_slot": 14,
2490
+ "day_index": 1,
2491
+ "day_name": "Tuesday",
2492
+ "period_index": 6,
2493
+ "room_id": "AI03 2F 02",
2494
+ "room_name": "AI03 2F 02 (ASD BLOCK)",
2495
+ "faculty_name": "Mhd Tahir Mirji / Nanda Kumar N",
2496
+ "subject_code": "DSA Lab",
2497
+ "section_id": "3DS-B2",
2498
+ "duration": 2
2499
+ },
2500
+ "PM with Git-3DS-BLOCK": {
2501
+ "start_slot": 12,
2502
+ "day_index": 1,
2503
+ "day_name": "Tuesday",
2504
+ "period_index": 4,
2505
+ "room_id": "AI09 1F26",
2506
+ "room_name": "AI09 1F26 (ECE BLOCK)",
2507
+ "faculty_name": "Mr. Sanjay P",
2508
+ "subject_code": "PM with Git",
2509
+ "section_id": "3DS",
2510
+ "duration": 2
2511
+ },
2512
+ "EDA Lab 2-3A-BLOCK": {
2513
+ "start_slot": 38,
2514
+ "day_index": 4,
2515
+ "day_name": "Friday",
2516
+ "period_index": 6,
2517
+ "room_id": "AI09 2F 14",
2518
+ "room_name": "AI09 2F 14 (ECE BLOCK)",
2519
+ "faculty_name": "Dr. Kavitha Nair R",
2520
+ "subject_code": "EDA Lab 2",
2521
+ "section_id": "3A",
2522
+ "duration": 2
2523
+ },
2524
+ "EDA Lab 2-3B-BLOCK": {
2525
+ "start_slot": 38,
2526
+ "day_index": 4,
2527
+ "day_name": "Friday",
2528
+ "period_index": 6,
2529
+ "room_id": "AI09 2F 13",
2530
+ "room_name": "AI09 2F 13 (ECE BLOCK)",
2531
+ "faculty_name": "Dr. Kavitha Nair R",
2532
+ "subject_code": "EDA Lab 2",
2533
+ "section_id": "3B",
2534
+ "duration": 2
2535
+ },
2536
+ "EDA Lab 2-3DS-BLOCK": {
2537
+ "start_slot": 38,
2538
+ "day_index": 4,
2539
+ "day_name": "Friday",
2540
+ "period_index": 6,
2541
+ "room_id": "AI09 3F 08",
2542
+ "room_name": "AI09 3F 08 (ECE BLOCK)",
2543
+ "faculty_name": "Dr. Kavitha Nair R",
2544
+ "subject_code": "EDA Lab 2",
2545
+ "section_id": "3DS",
2546
+ "duration": 2
2547
+ },
2548
+ "PM with Git 2-3A-BLOCK": {
2549
+ "start_slot": 38,
2550
+ "day_index": 4,
2551
+ "day_name": "Friday",
2552
+ "period_index": 6,
2553
+ "room_id": "AI09 2F 08",
2554
+ "room_name": "AI09 2F 08 (ECE BLOCK)",
2555
+ "faculty_name": "Mr. Sanjay P",
2556
+ "subject_code": "PM with Git 2",
2557
+ "section_id": "3A",
2558
+ "duration": 2
2559
+ },
2560
+ "PM with Git 2-3B-BLOCK": {
2561
+ "start_slot": 38,
2562
+ "day_index": 4,
2563
+ "day_name": "Friday",
2564
+ "period_index": 6,
2565
+ "room_id": "AI09 3F 03",
2566
+ "room_name": "AI09 3F 03 (ECE BLOCK)",
2567
+ "faculty_name": "Mr. Sanjay P",
2568
+ "subject_code": "PM with Git 2",
2569
+ "section_id": "3B",
2570
+ "duration": 2
2571
+ },
2572
+ "PM with Git 2-3DS-BLOCK": {
2573
+ "start_slot": 38,
2574
+ "day_index": 4,
2575
+ "day_name": "Friday",
2576
+ "period_index": 6,
2577
+ "room_id": "AI09 2F 12",
2578
+ "room_name": "AI09 2F 12 (ECE BLOCK)",
2579
+ "faculty_name": "Mr. Sanjay P",
2580
+ "subject_code": "PM with Git 2",
2581
+ "section_id": "3DS",
2582
+ "duration": 2
2583
+ }
2584
+ }
2585
+ }
schedule_versions.json ADDED
The diff for this file is too large to render. See raw diff
 
slm_inference.py ADDED
@@ -0,0 +1,384 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # slm_inference.py
2
+ import re, requests
3
+ from typing import List, Dict, Any, Optional
4
+
5
+ API_URL = 'https://vishwasmsme-timetable-slm-api.hf.space'
6
+ DAY_WORDS = {
7
+ 'monday':'MON','tuesday':'TUE','wednesday':'WED',
8
+ 'thursday':'THU','friday':'FRI','saturday':'SAT',
9
+ 'mon':'MON','tue':'TUE','wed':'WED',
10
+ 'thu':'THU','fri':'FRI','sat':'SAT',
11
+ }
12
+
13
+ def get_constraint(instruction):
14
+ try:
15
+ r = requests.post(f'{API_URL}/constraint',json={'instruction':instruction},timeout=60)
16
+ r.raise_for_status(); return r.json()
17
+ except Exception as e:
18
+ return {'success':False,'error':str(e),'constraints':[]}
19
+
20
+ def get_constraints_batch(instructions):
21
+ try:
22
+ r = requests.post(f'{API_URL}/constraints/batch',json={'instructions':instructions},timeout=120)
23
+ r.raise_for_status(); return r.json().get('all_constraints',[])
24
+ except Exception as e:
25
+ print(f'API Error: {e}'); return []
26
+
27
+ def check_api_health():
28
+ try:
29
+ return requests.get(f'{API_URL}/health',timeout=10).json().get('model_loaded',False)
30
+ except: return False
31
+
32
+ def smart_parse(prompt, faculties_data, subjects_data=None, sections_data=None):
33
+ p = prompt.lower().strip()
34
+ fac_map = _build_fac_map(faculties_data)
35
+ sub_map = _build_sub_map(subjects_data or [])
36
+ days = _extract_all_days(p)
37
+ period = _extract_period(p)
38
+
39
+ # NO FREE PERIOD — "first hour should not be free", "period 1 must have a class"
40
+ no_free_kw = ['should not be free', 'must not be free', 'cannot be free',
41
+ 'should always be occupied', 'always be occupied', 'always occupied',
42
+ 'no free slot in', 'free slot in period', 'no free in',
43
+ 'must have a class', 'should have a class', 'always occupied',
44
+ 'no free period', 'no free slot', 'must be filled',
45
+ 'first hour should not', 'last period should not']
46
+ if any(kw in p for kw in no_free_kw):
47
+ # Detect which period(s) are required
48
+ periods = []
49
+ if any(w in p for w in ['first hour', 'period 1', 'p1', 'first period', 'slot 1']):
50
+ periods = [0] # 0-indexed: P1
51
+ elif any(w in p for w in ['last hour', 'last period', 'last slot']):
52
+ periods = [-1] # will resolve to last period dynamically
53
+ elif any(w in p for w in ['second period', 'period 2', 'p2']):
54
+ periods = [1]
55
+ elif any(w in p for w in ['third period', 'period 3', 'p3']):
56
+ periods = [2]
57
+ else:
58
+ # Try to extract any period number mentioned
59
+ nums = re.findall(r'period\s*(\d+)|p\s*(\d+)|slot\s*(\d+)', p)
60
+ for g in nums:
61
+ n = next((int(x) for x in g if x), None)
62
+ if n: periods.append(n - 1)
63
+ if not periods:
64
+ periods = [0] # default: first period
65
+ return {
66
+ 'type': 'NO_FREE_PERIOD',
67
+ 'periods': periods,
68
+ 'section_id': _extract_section(p, sections_data),
69
+ }
70
+
71
+ # FACULTY SUBSTITUTION
72
+ if any(kw in p for kw in ['replace','substitute','take over','will take','on leave',
73
+ 'covering','cover for','instead of','will cover','deputed']):
74
+ facs = _find_two_faculties(p, fac_map)
75
+ if facs:
76
+ return {'type':'FACULTY_SUBSTITUTION','from_faculty_id':facs[0],
77
+ 'to_faculty_id':facs[1],'days':days,
78
+ 'subject_codes':_extract_subjects(p,sub_map)}
79
+
80
+ # FACULTY UNAVAILABLE / LEAVE (Single faculty)
81
+ if any(kw in p for kw in ['not available', 'unavailable', 'on leave', 'free on', 'day off', 'absent']):
82
+ single_fac = _extract_one_faculty(p, fac_map)
83
+ if single_fac and days:
84
+ return {
85
+ 'type': 'CANCEL_CLASS',
86
+ 'day': days[0],
87
+ 'period': period,
88
+ 'subject_code': None,
89
+ 'section_id': _extract_section(p, sections_data),
90
+ 'faculty_id': single_fac,
91
+ }
92
+
93
+ # NO SUBJECT ON DAY — "No TOC on Thursday", "No NLP class on Friday for 5A"
94
+ no_subj_kw = ['no toc', 'no nlp', 'no ml', 'no cn', 'no sepm', 'no nosql',
95
+ 'no rmipr', 'no iks', 'no evs', 'no dvlab', 'no cnlab',
96
+ 'no nlplab', 'no mllab', 'no miniproj', 'no major', 'no genai',
97
+ 'no devops', 'no hcai', 'no cc ', 'no forum', 'no softskill']
98
+ has_no_subj = any(kw in p for kw in no_subj_kw)
99
+ # Also catch "no [subject_name] class/on/for"
100
+ has_no_class = bool(re.search(r'no\s+\w+\s+(class|on|for)', p))
101
+ if has_no_subj or has_no_class:
102
+ subs = _extract_subjects(p, sub_map)
103
+ if subs and days:
104
+ return {
105
+ 'type': 'CANCEL_CLASS',
106
+ 'subject_code': subs[0],
107
+ 'day': days[0],
108
+ 'section_id': _extract_section(p, sections_data),
109
+ 'faculty_id': None,
110
+ 'period': None,
111
+ }
112
+
113
+ # CANCEL / HOLIDAY
114
+ holiday_kws = ['holiday','off day','public holiday','is a holiday','college is closed',
115
+ 'closed on','college closed','no classes on','no classes for all',
116
+ 'is holiday']
117
+ cancel_kws2 = ['cancel','no class','no lecture','cancelled','remove','delete','drop']
118
+ is_holiday = any(kw in p for kw in holiday_kws)
119
+ is_cancel = any(kw in p for kw in cancel_kws2)
120
+
121
+ # Whole-day clear: holiday OR (cancel/no classes with NO subject mentioned)
122
+ subs_found = _extract_subjects(p, sub_map)
123
+ fid_found = _extract_one_faculty(p, fac_map)
124
+ whole_day = is_holiday or (is_cancel and not subs_found and not fid_found and not period)
125
+
126
+ if (is_holiday or is_cancel) and days:
127
+ if whole_day:
128
+ return {'type':'MARK_HOLIDAY', 'days':days,
129
+ 'section_id':_extract_section(p, sections_data)}
130
+ return {'type':'CANCEL_CLASS', 'day':days[0], 'period':period,
131
+ 'subject_code': subs_found[0] if subs_found else None,
132
+ 'section_id': _extract_section(p, sections_data),
133
+ 'faculty_id': fid_found}
134
+
135
+ # CHANGE ROOM — detect "to Lab 2", "to R3", "shift all labs to L3"
136
+ # Must run BEFORE move/reschedule to prevent "Move NLPLAB to Lab 2" → RESCHEDULE_LAB
137
+ room_match = re.search(r'\b(lab\s*\d+|l\d+|r\d+|room\s*\d+)\b', p)
138
+ room_val = room_match.group(1).replace(' ','').upper() if room_match else None
139
+ room_dest_kws = ['to lab ', 'to l', 'to r', 'to room', 'change room',
140
+ 'assign room', 'move to lab', 'move to room', 'labs to']
141
+ has_room_dest = any(kw in p for kw in room_dest_kws) and room_val
142
+ if has_room_dest:
143
+ subs_r = _extract_subjects(p, sub_map)
144
+ return {'type': 'CHANGE_ROOM',
145
+ 'new_room_id': room_val,
146
+ 'room_id': room_val,
147
+ 'subject_code': subs_r[0] if subs_r else None,
148
+ 'section_id': _extract_section(p, sections_data),
149
+ 'day': days[0] if days else None,
150
+ 'all_labs': 'all' in p and 'lab' in p}
151
+
152
+ # MOVE / RESCHEDULE — only if NOT a cancel/remove keyword
153
+ move_kws = ['move','shift','reschedule','transfer','relocate']
154
+ cancel_kws = ['cancel','remove','delete','drop','no class','holiday']
155
+ is_move = any(kw in p for kw in move_kws)
156
+ is_cancel = any(kw in p for kw in cancel_kws)
157
+ if is_move and not is_cancel:
158
+ subs = _extract_subjects(p, sub_map)
159
+ is_lab = ('lab' in p or (subs and any(s.lower().endswith('lab') for s in subs)))
160
+ if is_lab and subs:
161
+ # "from X to Y" → days[0]=from, days[1]=to
162
+ # "to Y" only → days[0]=to, from_day=None
163
+ from_day = days[0] if len(days) >= 2 else None
164
+ to_day = days[1] if len(days) >= 2 else (days[0] if days else None)
165
+ return {'type':'RESCHEDULE_LAB',
166
+ 'subject_code': subs[0],
167
+ 'section_id': _extract_section(p, sections_data),
168
+ 'from_day': from_day,
169
+ 'to_day': to_day,
170
+ 'to_period': period or 1}
171
+ if len(days) >= 2:
172
+ subs = _extract_subjects(p, sub_map)
173
+ return {'type':'MOVE_CLASS',
174
+ 'from_day': days[0],
175
+ 'to_day': days[1],
176
+ 'subject_code': subs[0] if subs else None,
177
+ 'section_id': _extract_section(p, sections_data),
178
+ 'faculty_id': _extract_one_faculty(p, fac_map)}
179
+
180
+ # CHANGE ROOM
181
+ if any(kw in p for kw in ['change room','move to room','to lab','to room','assign room']):
182
+ rm = re.search(r'\b([rl]\d+|lab\s*\d*|room\s*\d+)\b', p)
183
+ return {'type':'CHANGE_ROOM',
184
+ 'room_id':rm.group(1).replace(' ','') if rm else None,
185
+ 'subject_code':(_extract_subjects(p,sub_map) or [None])[0],
186
+ 'section_id':_extract_section(p,sections_data),
187
+ 'day':days[0] if days else None}
188
+
189
+ # ADD EXTRA / MAKEUP
190
+ # Check for "schedule [subject] session/class" pattern like "Schedule extra ML session"
191
+ import re as _re
192
+ has_schedule_class = bool(_re.search(
193
+ r'(schedule|add)\s+(extra|additional|makeup|compensatory)?\s*\w+\s*(class|session)', p))
194
+ extra_keywords = ['extra class','makeup','additional class','make up',
195
+ 'compensatory','add class','schedule extra','extra session',
196
+ 'additional session','schedule.*class','add.*session']
197
+ if has_schedule_class or any(kw in p for kw in extra_keywords):
198
+ subs = _extract_subjects(p, sub_map)
199
+ return {'type':'ADD_EXTRA_CLASS',
200
+ 'subject_code':subs[0] if subs else None,
201
+ 'section_id':_extract_section(p,sections_data),
202
+ 'faculty_id':_extract_one_faculty(p,fac_map),
203
+ 'day':days[0] if days else None,
204
+ 'period': period or (5 if 'afternoon' in p else
205
+ 1 if 'morning' in p else None)}
206
+
207
+ # CHANGE FACULTY (permanent)
208
+ perm_kws = ['permanently','all classes','change faculty','hand over',
209
+ 'assign all','transfer all']
210
+ if any(kw in p for kw in perm_kws):
211
+ facs = _find_two_faculties(p, fac_map) or []
212
+ scodes = _extract_subjects(p, sub_map)
213
+ # "Assign all X to Y" / "Transfer A's classes to B" → to_fac = LAST mentioned
214
+ # "Change faculty from A to B" → from=A (first), to=B (second)
215
+ if len(facs) >= 2:
216
+ # "from A to B" order → from=facs[0], to=facs[1]
217
+ if 'from' in p and 'to' in p:
218
+ from_fac, to_fac = facs[0], facs[1]
219
+ else:
220
+ # "assign all ... to Syed" → to_fac is the one after "to"
221
+ to_idx = p.rfind(' to ')
222
+ if to_idx > 0:
223
+ after_to = p[to_idx+4:]
224
+ to_fac = next((fi for n,fi in fac_map.items() if n and n in after_to), facs[-1])
225
+ from_fac = next((fi for fi in facs if fi != to_fac), facs[0])
226
+ else:
227
+ from_fac, to_fac = facs[0], facs[1]
228
+ elif len(facs) == 0:
229
+ # Try single faculty extraction
230
+ single = _extract_one_faculty(p, fac_map)
231
+ from_fac, to_fac = None, single
232
+ elif len(facs) == 1:
233
+ # Only one faculty mentioned — they're the target (to_fac)
234
+ from_fac, to_fac = None, facs[0]
235
+ else:
236
+ from_fac, to_fac = None, None
237
+ if to_fac or (scodes and not facs):
238
+ # If no faculty found but subject found, still create constraint
239
+ # (partial — will match by subject code only)
240
+ return {'type':'CHANGE_FACULTY',
241
+ 'from_faculty_id': from_fac,
242
+ 'to_faculty_id': to_fac,
243
+ 'subject_codes': scodes,
244
+ 'subject_code': scodes[0] if scodes else None,
245
+ 'section_id':_extract_section(p,sections_data)}
246
+
247
+ # SWAP
248
+ if any(kw in p for kw in ['swap','exchange']):
249
+ import re as _re
250
+ periods = _re.findall(r'p(?:eriod)?\s*(\d+)', p)
251
+ subs = _extract_subjects(p, sub_map)
252
+ # Extract faculty ids in order of appearance in prompt
253
+ fac_found = sorted(
254
+ [(name, fid, p.index(name)) for name, fid in fac_map.items()
255
+ if name and name in p],
256
+ key=lambda x: x[2]
257
+ )
258
+ # Deduplicate by fid keeping first occurrence
259
+ seen, fac_ids = set(), []
260
+ for _, fid, _ in fac_found:
261
+ if fid not in seen:
262
+ fac_ids.append(fid); seen.add(fid)
263
+ return {'type': 'SWAP_CLASSES',
264
+ 'day1': days[0] if len(days) > 0 else None,
265
+ 'day2': days[1] if len(days) > 1 else None,
266
+ 'period1': int(periods[0]) if len(periods) > 0 else None,
267
+ 'period2': int(periods[1]) if len(periods) > 1 else None,
268
+ 'subject_code1': subs[0] if len(subs) > 0 else None,
269
+ 'subject_code2': subs[1] if len(subs) > 1 else None,
270
+ 'subject_codes': subs,
271
+ 'faculty_id1': fac_ids[0] if len(fac_ids) > 0 else None,
272
+ 'faculty_id2': fac_ids[1] if len(fac_ids) > 1 else None,
273
+ 'section_id': _extract_section(p, sections_data)}
274
+
275
+ # FREEZE
276
+ if any(kw in p for kw in ['freeze','lock slot','fix this slot','keep this',
277
+ 'lock ','don\'t move','do not move','should not change',
278
+ 'it should not change','fix mllab','fix nlplab',
279
+ 'fix cnlab','fix dvlab']):
280
+ subs = _extract_subjects(p, sub_map)
281
+ return {'type':'FREEZE_SLOT','day':days[0] if days else None,'period':period,
282
+ 'subject_code':subs[0] if subs else None,
283
+ 'section_id':_extract_section(p,sections_data)}
284
+
285
+ return None
286
+
287
+ def _build_fac_map(fds):
288
+ m = {}
289
+ for f in fds:
290
+ m[f['name'].lower()] = f['id']
291
+ short = re.sub(r'(prof\.|dr\.|mr\.|ms\.)\s*','',f['name'],flags=re.I).strip()
292
+ m[short.lower()] = f['id']
293
+ m[f['id'].lower()] = f['id']
294
+ return m
295
+
296
+ def _build_sub_map(sds):
297
+ m = {}
298
+ for s in sds:
299
+ m[s['name'].lower()] = s['code']
300
+ m[s['code'].lower()] = s['code']
301
+ return m
302
+
303
+ def _extract_all_days(text):
304
+ found, seen = [], set()
305
+ for w, d in DAY_WORDS.items():
306
+ idx = text.find(w)
307
+ if idx >= 0 and d not in seen:
308
+ found.append((idx,d)); seen.add(d)
309
+ found.sort(); return [d for _,d in found]
310
+
311
+ def _extract_period(text):
312
+ m = re.search(r'p(?:eriod)?\s*(\d+)', text)
313
+ return int(m.group(1)) if m else None
314
+
315
+ def _extract_subjects(text, sub_map):
316
+ # Sort by name length descending so "nlplab" matches before "nlp"
317
+ sorted_items = sorted(sub_map.items(), key=lambda x: -len(x[0]))
318
+ seen_codes, seen_names, results = set(), set(), []
319
+ for name, code in sorted_items:
320
+ if not name or name not in text: continue
321
+ if code in seen_codes: continue
322
+ # Skip if a longer name already matched that contains this name
323
+ # e.g. skip "nlp" if "nlplab" already matched
324
+ already_covered = any(matched for matched in seen_names if name in matched)
325
+ if already_covered: continue
326
+ results.append(code)
327
+ seen_codes.add(code)
328
+ seen_names.add(name)
329
+ return results
330
+
331
+ def _extract_one_faculty(text, fac_map):
332
+ for name,fid in fac_map.items():
333
+ if name and name in text: return fid
334
+ return None
335
+
336
+ def _find_two_faculties(text, fac_map):
337
+ found = []
338
+ for name,fid in sorted(fac_map.items(), key=lambda x:-len(x[0])):
339
+ if name and name in text and fid not in found:
340
+ found.append(fid)
341
+ if len(found)==2: break
342
+ return found if len(found)==2 else None
343
+
344
+ def _extract_section(text, sections_data):
345
+ if not sections_data: return None
346
+ # Sort by length descending so "6a-e1" matches before "6a"
347
+ for s in sorted(sections_data, key=lambda x: -len(x['id'])):
348
+ if s['id'].lower() in text:
349
+ return s['id']
350
+ # Match shorthand like "6a" -> return prefix as parent filter
351
+ # e.g. "6a" matches 6a-E1 AND 6a-E2 -> return "6A" (prefix)
352
+ # caller uses this to filter all sub-sections of 6A
353
+ prefixes = {}
354
+ for s in sections_data:
355
+ prefix = s['id'].split('-')[0].lower()
356
+ prefixes.setdefault(prefix, []).append(s['id'])
357
+ for prefix, ids in sorted(prefixes.items(), key=lambda x: -len(x[0])):
358
+ pattern = r'(?<![a-z0-9])' + re.escape(prefix) + r'(?![a-z0-9])'
359
+ if re.search(pattern, text):
360
+ if len(ids) == 1:
361
+ return ids[0] # unique — return full id
362
+ else:
363
+ return prefix.upper() # ambiguous — return parent "6A", "5A" etc.
364
+ return None
365
+ # Sort by length descending so "6a-e1" matches before "6a"
366
+ for s in sorted(sections_data, key=lambda x: -len(x['id'])):
367
+ if s['id'].lower() in text:
368
+ return s['id']
369
+ # Match shorthand like "6a" -> returns None if multiple sections share prefix
370
+ # e.g. "6a" matches 6a-E1 AND 6a-E2, so return None (move all of them)
371
+ prefixes = {}
372
+ for s in sections_data:
373
+ prefix = s['id'].split('-')[0].lower()
374
+ prefixes.setdefault(prefix, []).append(s['id'])
375
+ for prefix, ids in prefixes.items():
376
+ if re.search(r'' + prefix + r'', text):
377
+ if len(ids) == 1:
378
+ return ids[0] # unique match
379
+ # multiple sections with same prefix — return None (affects all)
380
+ return None
381
+
382
+ def parse_substitution_prompt(prompt, faculties_data):
383
+ result = smart_parse(prompt, faculties_data)
384
+ return result if result and result.get('type')=='FACULTY_SUBSTITUTION' else {}
solver.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # solver.py
2
+
3
+ from typing import List, Dict, Optional, Any, Tuple
4
+ from ortools.sat.python import cp_model
5
+ import os
6
+
7
+ from models import Task, Faculty, Section, Room
8
+ from constraint_engine import ConstraintEngine
9
+
10
+ class TimetableSolver:
11
+
12
+ def __init__(self, tasks, faculties, sections, rooms):
13
+ self.tasks = tasks
14
+ self.faculties = faculties
15
+ self.sections = sections
16
+ self.rooms = rooms
17
+ self.model = cp_model.CpModel()
18
+ self.solver = cp_model.CpSolver()
19
+ self.constraint_engine = None
20
+ self.objective_engine = None
21
+
22
+ def solve(
23
+ self,
24
+ time_limit_seconds: int = 60,
25
+ enable_soft_constraints: bool = True,
26
+ soft_constraint_weights: Dict[str, int] = None,
27
+ log_search_progress: bool = True,
28
+ num_workers: int = None,
29
+ slm_constraints: List[Dict[str, Any]] = None,
30
+ scheduling_rules: List[Dict[str, Any]] = None,
31
+ locked_schedule: Dict[str, Any] = None,
32
+ locked_semesters: List[int] = None,
33
+ ) -> Tuple[str, Optional[Dict[str, Any]]]:
34
+ if num_workers is None:
35
+ num_workers = os.cpu_count() or 8
36
+
37
+ # 1. Hard constraints
38
+ print("Initializing Constraint Engine...")
39
+ self.constraint_engine = ConstraintEngine(
40
+ model=self.model, tasks=self.tasks, faculties=self.faculties,
41
+ sections=self.sections, rooms=self.rooms,
42
+ slm_constraints=slm_constraints, scheduling_rules=scheduling_rules,
43
+ locked_schedule=locked_schedule, locked_semesters=locked_semesters
44
+ )
45
+ self.constraint_engine.apply_all_constraints()
46
+
47
+ # 2. Soft constraints
48
+ if enable_soft_constraints:
49
+ print("Initializing Objective Engine...")
50
+ from objective_engine import ObjectiveEngine
51
+ self.objective_engine = ObjectiveEngine(
52
+ model=self.model,
53
+ ce=self.constraint_engine,
54
+ tasks=self.tasks, faculties=self.faculties,
55
+ sections=self.sections,
56
+ weights=soft_constraint_weights
57
+ )
58
+ self.objective_engine.build_objective()
59
+
60
+ # 4. Configure solver
61
+ self.solver.parameters.max_time_in_seconds = time_limit_seconds
62
+ self.solver.parameters.log_search_progress = log_search_progress
63
+ self.solver.parameters.num_search_workers = num_workers
64
+
65
+ # 5. Solve
66
+ print(f"Starting solver (Limit: {time_limit_seconds}s)...")
67
+ status_val = self.solver.Solve(self.model)
68
+
69
+ status_map = {
70
+ cp_model.OPTIMAL: "OPTIMAL",
71
+ cp_model.FEASIBLE: "FEASIBLE",
72
+ cp_model.INFEASIBLE: "INFEASIBLE",
73
+ cp_model.MODEL_INVALID: "MODEL_INVALID",
74
+ cp_model.UNKNOWN: "UNKNOWN"
75
+ }
76
+ status_str = status_map.get(status_val, "UNKNOWN")
77
+ print(f"Solver finished: {status_str}")
78
+
79
+ solution = None
80
+ if status_val in (cp_model.OPTIMAL, cp_model.FEASIBLE):
81
+ solution = self._extract_solution()
82
+ print(f"Solution found! Objective: {self.solver.ObjectiveValue()}")
83
+ else:
84
+ print("No solution found.")
85
+
86
+ return status_str, solution
87
+
88
+ def _extract_solution(self):
89
+ import constants as const
90
+ schedule = {}
91
+ for task in self.tasks:
92
+ start_var, _, _, room_var = self.constraint_engine.task_vars[task.task_id]
93
+ start_slot = self.solver.Value(start_var)
94
+ room_index = self.solver.Value(room_var)
95
+ assigned_room = self.rooms[room_index]
96
+ day_index = start_slot // const.NUM_TEACHING_SLOTS_PER_DAY
97
+ period_index = start_slot % const.NUM_TEACHING_SLOTS_PER_DAY
98
+ schedule[task.task_id] = {
99
+ "task_obj": task,
100
+ "start_slot": start_slot,
101
+ "day_index": day_index,
102
+ "day_name": const.DAYS[day_index],
103
+ "period_index": period_index,
104
+ "room_id": assigned_room.room_id,
105
+ "room_name": f"{assigned_room.room_id} ({assigned_room.building})",
106
+ "faculty_name": task.faculty.name,
107
+ "subject_code": task.subject.subject_code,
108
+ "section_id": task.section.section_id,
109
+ "duration": task.duration
110
+ }
111
+ return schedule
112
+
113
+ def diagnose_infeasibility(self) -> str:
114
+ """Heuristic checks to explain common reasons for INFEASIBLE models."""
115
+ import constants as const
116
+ # 1. Check Faculty Overload
117
+ fac_hours = {}
118
+ for t in self.tasks:
119
+ if 'DUMMY' in t.faculty.id: continue
120
+ for fid in t.faculty.id.split('_'):
121
+ fac_hours[fid] = fac_hours.get(fid, 0) + t.duration
122
+
123
+ for fid, hrs in fac_hours.items():
124
+ if hrs > const.TOTAL_TEACHING_SLOTS_PER_WEEK:
125
+ fac = next((f.name for f in self.faculties if f.id == fid), fid)
126
+ return f"Faculty overload: {fac} ({fid}) is assigned {hrs} hours of classes, but there are only {const.TOTAL_TEACHING_SLOTS_PER_WEEK} total slots available in the week."
127
+
128
+ # 2. Check Section Overload
129
+ sec_parent_hours = {}
130
+ sec_batch_hours = {}
131
+ for t in self.tasks:
132
+ sec = t.section.section_id
133
+ if '-' in sec:
134
+ parent = sec.split('-')[0]
135
+ if parent not in sec_batch_hours:
136
+ sec_batch_hours[parent] = {}
137
+ sec_batch_hours[parent][sec] = sec_batch_hours[parent].get(sec, 0) + t.duration
138
+ else:
139
+ sec_parent_hours[sec] = sec_parent_hours.get(sec, 0) + t.duration
140
+
141
+ for sid, hrs in sec_parent_hours.items():
142
+ max_batch = 0
143
+ if sid in sec_batch_hours:
144
+ max_batch = max(sec_batch_hours[sid].values())
145
+ total = hrs + max_batch
146
+ if total > const.TOTAL_TEACHING_SLOTS_PER_WEEK:
147
+ return f"Section overload: Section {sid} (including its batches) requires {total} hours of classes, but there are only {const.TOTAL_TEACHING_SLOTS_PER_WEEK} slots available in the week."
148
+
149
+ return "The constraint engine could not find a solution due to conflicting constraints. This typically happens if multiple subjects are forced to occur at the same time, or if teachers/rooms are double-booked by rigid scheduling rules."
storage.py ADDED
@@ -0,0 +1,273 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # storage.py
2
+ # Handles all persistent storage: timetable data, saved schedules, change history
3
+ # Uses HuggingFace Hub CommitScheduler to persist data across Space restarts.
4
+
5
+ import json
6
+ import os
7
+ from datetime import datetime
8
+ from typing import Dict, Any, Optional
9
+ from pathlib import Path
10
+ import threading
11
+ import uuid
12
+
13
+ # ── HuggingFace Persistent Storage Setup ─────────────────────────────────
14
+ # On HuggingFace Spaces, files written at runtime are EPHEMERAL.
15
+ # CommitScheduler auto-commits changed files back to the repo periodically.
16
+
17
+ _scheduler = None
18
+ _scheduler_lock = threading.Lock()
19
+
20
+ # Directory where persistent JSON files are stored
21
+ DATA_DIR = Path(__file__).resolve().parent # Always use the directory where storage.py is located
22
+
23
+ DATA_FILE = str(DATA_DIR / 'timetable_data.json')
24
+ SCHEDULE_FILE = str(DATA_DIR / 'saved_schedule.json')
25
+ HISTORY_FILE = str(DATA_DIR / 'change_history.json')
26
+ LEAVE_FILE = str(DATA_DIR / 'leave_requests.json')
27
+ SUB_REQ_FILE = str(DATA_DIR / 'substitution_requests.json')
28
+ ORIGINAL_FILE = str(DATA_DIR / 'original_schedule.json')
29
+ VERSIONS_FILE = str(DATA_DIR / 'schedule_versions.json')
30
+ PENDING_FILE = str(DATA_DIR / 'pending_timetables.json')
31
+ CANCELLATION_FILE = str(DATA_DIR / 'cancellations.json')
32
+
33
+ def _get_scheduler():
34
+ """Lazily initialize the CommitScheduler (only on HuggingFace Spaces)."""
35
+ global _scheduler
36
+ if _scheduler is not None:
37
+ return _scheduler
38
+
39
+ with _scheduler_lock:
40
+ if _scheduler is not None:
41
+ return _scheduler
42
+
43
+ # Only enable on HuggingFace Spaces (SPACE_ID env var is set)
44
+ space_id = os.environ.get("SPACE_ID")
45
+ if not space_id:
46
+ return None # Running locally, no scheduler needed
47
+
48
+ try:
49
+ from huggingface_hub import CommitScheduler, HfApi
50
+
51
+ # Workaround: CommitScheduler internally calls create_repo()
52
+ # which requires space_sdk for Spaces, but CommitScheduler
53
+ # doesn't accept/forward that parameter. Temporarily patch
54
+ # create_repo to inject space_sdk='docker' as a default.
55
+ _orig_create = HfApi.create_repo
56
+
57
+ def _patched_create(self, *args, **kwargs):
58
+ kwargs.setdefault("space_sdk", "docker")
59
+ return _orig_create(self, *args, **kwargs)
60
+
61
+ HfApi.create_repo = _patched_create
62
+
63
+ _scheduler = CommitScheduler(
64
+ repo_id=space_id,
65
+ repo_type="space",
66
+ folder_path=str(DATA_DIR),
67
+ path_in_repo=".",
68
+ every=2, # Commit every 2 minutes
69
+ allow_patterns=[
70
+ "*.json", # All JSON data files
71
+ ],
72
+ squash_history=True, # Keep repo history clean
73
+ )
74
+
75
+ # Restore original create_repo
76
+ HfApi.create_repo = _orig_create
77
+
78
+ print(f"[storage] CommitScheduler initialized for {space_id}")
79
+ except Exception as e:
80
+ print(f"[storage] CommitScheduler failed to initialize: {e}")
81
+ _scheduler = None
82
+
83
+ return _scheduler
84
+
85
+
86
+ def _safe_write_json(filepath: str, data):
87
+ """Write JSON to file. The CommitScheduler will auto-commit changes."""
88
+ # Get scheduler to ensure it's initialized (will auto-detect file changes)
89
+ _get_scheduler()
90
+
91
+ with open(filepath, 'w') as f:
92
+ json.dump(data, f, indent=2)
93
+
94
+
95
+ def _safe_read_json(filepath: str, default=None):
96
+ """Read JSON from file with error handling."""
97
+ if not os.path.exists(filepath):
98
+ return default
99
+ try:
100
+ with open(filepath, 'r') as f:
101
+ return json.load(f)
102
+ except (json.JSONDecodeError, IOError):
103
+ return default
104
+
105
+
106
+ # ── Timetable Data (faculties, subjects, sections etc.) ──────────────────
107
+ def load_data() -> Dict:
108
+ result = _safe_read_json(DATA_FILE)
109
+ if result:
110
+ return result
111
+ return {'faculties': [], 'subjects': [], 'sections': [],
112
+ 'rooms': [], 'allocations': []}
113
+
114
+ def save_data(data: Dict):
115
+ _safe_write_json(DATA_FILE, data)
116
+
117
+ # ── Saved Schedule (the fixed semester timetable) ────────────────────────
118
+ def save_schedule(solution: Dict[str, Any]):
119
+ """Save the generated timetable as the fixed semester schedule."""
120
+ serializable = {}
121
+ for task_id, info in solution.items():
122
+ serializable[task_id] = {k: v for k, v in info.items() if k != 'task_obj'}
123
+
124
+ payload = {
125
+ 'generated_at': datetime.now().isoformat(),
126
+ 'schedule': serializable
127
+ }
128
+ _safe_write_json(SCHEDULE_FILE, payload)
129
+
130
+ def load_schedule() -> Optional[Dict]:
131
+ """Load the saved semester schedule."""
132
+ return _safe_read_json(SCHEDULE_FILE)
133
+
134
+ def schedule_exists() -> bool:
135
+ return os.path.exists(SCHEDULE_FILE)
136
+
137
+ def clear_schedule():
138
+ if os.path.exists(SCHEDULE_FILE):
139
+ os.remove(SCHEDULE_FILE)
140
+
141
+ # ── Original Schedule (snapshot taken at generation time) ───────────────
142
+
143
+ def save_original_schedule(solution: Dict[str, Any]):
144
+ """Save a permanent snapshot of the originally generated timetable."""
145
+ serializable = {}
146
+ for task_id, info in solution.items():
147
+ serializable[task_id] = {k: v for k, v in info.items() if k != 'task_obj'}
148
+ payload = {
149
+ 'generated_at': datetime.now().isoformat(),
150
+ 'schedule': serializable
151
+ }
152
+ _safe_write_json(ORIGINAL_FILE, payload)
153
+
154
+ def load_original_schedule() -> Optional[Dict]:
155
+ """Load the original (never-modified) timetable snapshot."""
156
+ return _safe_read_json(ORIGINAL_FILE)
157
+
158
+ def original_schedule_exists() -> bool:
159
+ return os.path.exists(ORIGINAL_FILE)
160
+
161
+ def clear_original_schedule():
162
+ if os.path.exists(ORIGINAL_FILE):
163
+ os.remove(ORIGINAL_FILE)
164
+
165
+ # ── Change History ────────────────────────────────────────────────────────
166
+ def load_history() -> list:
167
+ return _safe_read_json(HISTORY_FILE, default=[])
168
+
169
+ def add_history_entry(operation_type: str, description: str, affected_sections: list, changes: list, status: str = "SUCCESS", constraints: list = None):
170
+ history = load_history()
171
+ history.append({
172
+ 'id': str(uuid.uuid4()),
173
+ 'timestamp': datetime.now().isoformat(),
174
+ 'operation_type': operation_type,
175
+ 'description': description,
176
+ 'affected_sections': affected_sections or [],
177
+ 'changes': changes or [],
178
+ 'status': status,
179
+ 'constraints': constraints or [],
180
+ })
181
+ _safe_write_json(HISTORY_FILE, history)
182
+
183
+ def save_history(history: list):
184
+ _safe_write_json(HISTORY_FILE, history)
185
+
186
+ def clear_history():
187
+ if os.path.exists(HISTORY_FILE):
188
+ os.remove(HISTORY_FILE)
189
+
190
+ # ── Schedule Versions ─────────────────────────────────────────────────────
191
+
192
+ def load_versions() -> list:
193
+ return _safe_read_json(VERSIONS_FILE, default=[])
194
+
195
+ def save_version(label: str = None):
196
+ """Snapshot the current schedule + history as a version before clearing."""
197
+ if not schedule_exists():
198
+ return None
199
+ sched = load_schedule()
200
+ if not sched:
201
+ return None
202
+ history = load_history()
203
+ versions = load_versions()
204
+ version_id = str(uuid.uuid4())
205
+ version_num = len(versions) + 1
206
+ auto_label = label or f"Version {version_num}"
207
+ versions.append({
208
+ 'id': version_id,
209
+ 'label': auto_label,
210
+ 'timestamp': datetime.now().isoformat(),
211
+ 'generated_at': sched.get('generated_at', ''),
212
+ 'schedule': sched.get('schedule', {}),
213
+ 'history': history,
214
+ })
215
+ _safe_write_json(VERSIONS_FILE, versions)
216
+ return version_id
217
+
218
+ def restore_version(version_id: str):
219
+ """Restore a saved version as the active schedule."""
220
+ versions = load_versions()
221
+ target = next((v for v in versions if v['id'] == version_id), None)
222
+ if not target:
223
+ return None
224
+ # Save the schedule (the raw dict, not the wrapper)
225
+ save_schedule(target['schedule'])
226
+ # Restore history
227
+ _safe_write_json(HISTORY_FILE, target.get('history', []))
228
+ return target
229
+
230
+ # ── Pending Proposals ─────────────────────────────────────────────────────
231
+
232
+ def load_proposals() -> list:
233
+ return _safe_read_json(PENDING_FILE, default=[])
234
+
235
+ def save_proposal(proposal: dict):
236
+ proposals = load_proposals()
237
+ proposals.append(proposal)
238
+ _safe_write_json(PENDING_FILE, proposals)
239
+
240
+ def delete_proposal(proposal_id: str):
241
+ proposals = load_proposals()
242
+ proposals = [p for p in proposals if p.get('id') != proposal_id]
243
+ _safe_write_json(PENDING_FILE, proposals)
244
+
245
+ # ── Leave Requests ────────────────────────────────────────────────────────
246
+
247
+ def load_leave_requests() -> list:
248
+ return _safe_read_json(LEAVE_FILE, default=[])
249
+
250
+ def save_leave_requests(requests: list):
251
+ _safe_write_json(LEAVE_FILE, requests)
252
+
253
+ def clear_leave_requests():
254
+ if os.path.exists(LEAVE_FILE): os.remove(LEAVE_FILE)
255
+
256
+ # ── Substitution Requests ─────────────────────────────────────────────────
257
+
258
+ def load_substitution_requests() -> list:
259
+ return _safe_read_json(SUB_REQ_FILE, default=[])
260
+
261
+ def save_substitution_requests(requests: list):
262
+ _safe_write_json(SUB_REQ_FILE, requests)
263
+
264
+ def clear_substitution_requests():
265
+ if os.path.exists(SUB_REQ_FILE): os.remove(SUB_REQ_FILE)
266
+
267
+ # ── Cancellation Requests ─────────────────────────────────────────────────
268
+
269
+ def load_cancellations() -> list:
270
+ return _safe_read_json(CANCELLATION_FILE, default=[])
271
+
272
+ def save_cancellations(requests: list):
273
+ _safe_write_json(CANCELLATION_FILE, requests)
substitution_engine.py ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import uuid
2
+ from datetime import datetime, timedelta
3
+ import json
4
+ from typing import List, Optional, Dict, Any
5
+
6
+ from models import (
7
+ LeaveRequest, LeaveStatus, SubstitutionRequest, SubstitutionStatus,
8
+ AffectedSlot, ProposedSwap
9
+ )
10
+ from storage import (
11
+ load_schedule, load_data, save_schedule, add_history_entry,
12
+ load_substitution_requests, save_substitution_requests,
13
+ load_leave_requests, save_leave_requests
14
+ )
15
+ import constants as const
16
+
17
+ def _get_faculty_id_by_name(name: str, faculties: list) -> Optional[str]:
18
+ """Resolve a faculty display name (e.g. 'Prof. Anu') to its internal ID ('anu')."""
19
+ name_clean = name.replace("Prof. ", "").replace("Dr. ", "").replace("Mr. ", "").replace("Ms. ", "").strip().lower()
20
+ # Exact ID match first
21
+ for f in faculties:
22
+ if name_clean == f["id"].lower():
23
+ return f["id"]
24
+ # Substring / partial match
25
+ for f in faculties:
26
+ if name_clean in f["id"].lower() or name_clean in f["name"].lower():
27
+ return f["id"]
28
+ # Also check the other direction
29
+ if f["id"].lower() in name_clean or f["name"].replace("Prof. ", "").replace("Dr. ", "").replace("Mr. ", "").replace("Ms. ", "").strip().lower() in name_clean:
30
+ return f["id"]
31
+ # Word-level match: check if any word in the input matches a faculty ID
32
+ for word in name_clean.split():
33
+ for f in faculties:
34
+ if word == f["id"].lower():
35
+ return f["id"]
36
+ return None
37
+
38
+ def _get_faculty_name_by_id(fac_id: str, faculties: list) -> str:
39
+ for f in faculties:
40
+ if f["id"] == fac_id:
41
+ return f["name"]
42
+ return fac_id
43
+
44
+ def _resolve_faculty_id(raw_id: str, faculties: list) -> Optional[str]:
45
+ """
46
+ Given a raw faculty identifier (could be an internal ID like 'anu',
47
+ a display name like 'Prof. Anu', or a user full name like 'Anu Sharma'),
48
+ resolve it to the canonical internal faculty ID.
49
+ """
50
+ # 1. Direct match on ID
51
+ for f in faculties:
52
+ if f["id"].lower() == raw_id.lower():
53
+ return f["id"]
54
+ # 2. Direct match on name
55
+ for f in faculties:
56
+ if f["name"].lower() == raw_id.lower():
57
+ return f["id"]
58
+ # 3. Try _get_faculty_id_by_name (handles prefix stripping)
59
+ result = _get_faculty_id_by_name(raw_id, faculties)
60
+ if result:
61
+ return result
62
+ # 4. Word-level: check each word of raw_id against faculty IDs
63
+ words = raw_id.replace("Prof. ", "").replace("Dr. ", "").replace("Mr. ", "").replace("Ms. ", "").strip().lower().split()
64
+ for word in words:
65
+ if len(word) < 2: # skip very short words
66
+ continue
67
+ for f in faculties:
68
+ if word == f["id"].lower():
69
+ return f["id"]
70
+ fname_clean = f["name"].replace("Prof. ", "").replace("Dr. ", "").replace("Mr. ", "").replace("Ms. ", "").strip().lower()
71
+ if word == fname_clean or word in fname_clean.split():
72
+ return f["id"]
73
+ return None
74
+
75
+ def _parent_section(section_id: str) -> str:
76
+ """Merge sub-sections like '6b-E1', '6b-E2' into parent '6b'."""
77
+ return section_id.split("-")[0] if "-" in section_id else section_id
78
+
79
+ def process_leave_approval(leave: LeaveRequest):
80
+ """Entry point when a leave is approved. Finds affected slots and starts substitution process."""
81
+ schedule_data = load_schedule()
82
+ if not schedule_data or not schedule_data.get("schedule"):
83
+ return
84
+
85
+ data = load_data()
86
+ faculties = data.get("faculties", [])
87
+ allocations = data.get("allocations", [])
88
+
89
+ # Resolve the leave's faculty_id to the canonical internal ID.
90
+ # The frontend may send a display name like "Anu Sharma" instead of "anu".
91
+ resolved_fac_id = _resolve_faculty_id(leave.faculty_id, faculties)
92
+ if not resolved_fac_id:
93
+ # Fallback: try using the raw value
94
+ resolved_fac_id = leave.faculty_id
95
+
96
+ leave_fac_name = _get_faculty_name_by_id(resolved_fac_id, faculties)
97
+
98
+ affected_tasks = []
99
+ current_solution = schedule_data["schedule"]
100
+
101
+ # Track which (parent_section, subject, day, period) combos we've already added
102
+ # to avoid duplicate requests for E1/E2 batches of the same class
103
+ seen_slots = set()
104
+
105
+ # Identify affected classes for the given days
106
+ for task_id, info in current_solution.items():
107
+ faculty_name = info.get("faculty_name", "")
108
+ # Resolve this task's faculty to an internal ID for reliable comparison
109
+ task_fac_id = _get_faculty_id_by_name(faculty_name, faculties)
110
+
111
+ day_index = info.get("day_index", 0)
112
+ day_name = const.DAYS[day_index] if day_index < len(const.DAYS) else f"Day {day_index}"
113
+
114
+ # Check if the task belongs to the leaving faculty and on one of their leave days
115
+ if task_fac_id and task_fac_id == resolved_fac_id and day_name in leave.days:
116
+ # Handle duration (labs are 2 hours, theory 1 hour)
117
+ duration = info.get("duration", 1)
118
+ period = info.get("period_index", 0)
119
+ subject = info.get("subject_code", "")
120
+ parent_sec = _parent_section(info.get("section_id", ""))
121
+
122
+ for i in range(duration):
123
+ slot_key = (subject, parent_sec, day_name, period + i)
124
+ if slot_key in seen_slots:
125
+ continue # Skip E2 if E1 already added for same class
126
+ seen_slots.add(slot_key)
127
+
128
+ affected_tasks.append(AffectedSlot(
129
+ subject_code=subject,
130
+ section_id=parent_sec, # Use parent section (e.g., '6b' not '6b-E1')
131
+ day=day_name,
132
+ period=period + i,
133
+ room_id=info.get("room_id", "")
134
+ ))
135
+
136
+ # For each affected slot, find substitutes
137
+ for slot in affected_tasks:
138
+ find_substitutes_for_slot(slot, resolved_fac_id, leave.leave_id, current_solution, data)
139
+
140
+ def find_substitutes_for_slot(slot: AffectedSlot, leave_faculty_id: str, leave_id: str, current_solution: dict, data: dict):
141
+ """Applies the Priority Ladder to find substitutes."""
142
+ faculties = data.get("faculties", [])
143
+
144
+ # Find all faculties teaching the same subject anywhere
145
+ teach_same_subject = set()
146
+ for task_id, info in current_solution.items():
147
+ if info.get("subject_code") == slot.subject_code:
148
+ fac_id = _get_faculty_id_by_name(info.get("faculty_name", ""), faculties)
149
+ if fac_id and fac_id != leave_faculty_id:
150
+ teach_same_subject.add(fac_id)
151
+
152
+ # Build a map of faculty busyness
153
+ busyness = {f["id"]: [] for f in faculties}
154
+ day_idx = const.DAYS.index(slot.day) if slot.day in const.DAYS else -1
155
+ for task_id, info in current_solution.items():
156
+ if info.get("day_index") == day_idx:
157
+ fac_id = _get_faculty_id_by_name(info.get("faculty_name", ""), faculties)
158
+ if fac_id:
159
+ duration = info.get("duration", 1)
160
+ start_p = info.get("period_index", 0)
161
+ for i in range(duration):
162
+ busyness[fac_id].append(start_p + i)
163
+
164
+ # Also exclude anyone who is currently on an approved leave for this day
165
+ leave_requests = load_leave_requests()
166
+ on_leave_today = set()
167
+ # Always include the leaving faculty
168
+ on_leave_today.add(leave_faculty_id)
169
+ for lr in leave_requests:
170
+ if lr.get("status") == "APPROVED" and slot.day in lr.get("days", []):
171
+ # Resolve the raw faculty_id to internal ID so it matches
172
+ resolved = _resolve_faculty_id(lr.get("faculty_id", ""), faculties)
173
+ if resolved:
174
+ on_leave_today.add(resolved)
175
+ on_leave_today.add(lr.get("faculty_id")) # keep raw value too
176
+
177
+ candidates_p1 = [] # Same subject, free
178
+ candidates_p2 = [] # Same subject, busy
179
+ candidates_p3 = [] # Free, any subject
180
+
181
+ # Priority 1 & 2
182
+ for fac_id in teach_same_subject:
183
+ if fac_id in on_leave_today: continue
184
+ if slot.period not in busyness.get(fac_id, []):
185
+ candidates_p1.append(fac_id)
186
+ else:
187
+ candidates_p2.append(fac_id)
188
+
189
+ # Priority 3
190
+ for fac in faculties:
191
+ fac_id = fac["id"]
192
+ if fac_id == leave_faculty_id or fac_id in on_leave_today: continue
193
+ if slot.period not in busyness.get(fac_id, []) and fac_id not in candidates_p1:
194
+ candidates_p3.append(fac_id)
195
+
196
+ # Dispatch logic
197
+ if candidates_p1:
198
+ _dispatch_requests(candidates_p1, slot, leave_faculty_id, leave_id, 1)
199
+ elif candidates_p2:
200
+ # P2 needs a swap. For simplicity, just pick a random free slot of the candidate
201
+ # that the leaving teacher is not using. This requires a bit more logic.
202
+ # For MVP, we'll try to find any free slot for candidate `f` and propose a swap.
203
+ for fac_id in candidates_p2:
204
+ free_slots = [p for p in range(const.NUM_TEACHING_SLOTS_PER_DAY) if p not in busyness[fac_id] and p not in busyness.get(leave_faculty_id, [])]
205
+ if free_slots:
206
+ swap = ProposedSwap(
207
+ subject_code=slot.subject_code,
208
+ day=slot.day,
209
+ original_period=slot.period,
210
+ new_period=free_slots[0]
211
+ )
212
+ _dispatch_requests([fac_id], slot, leave_faculty_id, leave_id, 2, swap)
213
+ return # Just propose one for now
214
+
215
+ # Fallback to P3 if no swaps found
216
+ if candidates_p3:
217
+ _dispatch_requests(candidates_p3, slot, leave_faculty_id, leave_id, 3)
218
+ elif candidates_p3:
219
+ _dispatch_requests(candidates_p3, slot, leave_faculty_id, leave_id, 3)
220
+ else:
221
+ # Priority 4: Nobody available, escalate to Admin.
222
+ pass
223
+
224
+ def _dispatch_requests(candidates: list, slot: AffectedSlot, original_fac_id: str, leave_id: str, priority: int, swap: Optional[ProposedSwap] = None):
225
+ requests = load_substitution_requests()
226
+ now = datetime.now()
227
+ expires = now + timedelta(hours=24)
228
+
229
+ for fac_id in candidates:
230
+ # NEVER send a substitute request to the original faculty
231
+ if fac_id == original_fac_id:
232
+ continue
233
+ # don't send duplicates
234
+ existing = [r for r in requests if r["leave_id"] == leave_id and r["affected_slot"]["period"] == slot.period and r["affected_slot"]["section_id"] == slot.section_id and r["candidate_faculty_id"] == fac_id and r["status"] == "PENDING"]
235
+ if existing: continue
236
+
237
+ req = SubstitutionRequest(
238
+ request_id=str(uuid.uuid4()),
239
+ leave_id=leave_id,
240
+ affected_slot=slot,
241
+ original_faculty_id=original_fac_id,
242
+ candidate_faculty_id=fac_id,
243
+ priority_level=priority,
244
+ status=SubstitutionStatus.PENDING,
245
+ sent_at=now.isoformat(),
246
+ expires_at=expires.isoformat(),
247
+ proposed_swap=swap
248
+ )
249
+ import dataclasses
250
+ requests.append(dataclasses.asdict(req))
251
+
252
+ save_substitution_requests(requests)
253
+
254
+ def handle_acceptance(request_id: str):
255
+ requests = load_substitution_requests()
256
+ target_req = next((r for r in requests if r["request_id"] == request_id), None)
257
+ if not target_req or target_req["status"] != "PENDING":
258
+ return False, "Request not found or not pending."
259
+
260
+ target_req["status"] = "ACCEPTED"
261
+ target_req["responded_at"] = datetime.now().isoformat()
262
+
263
+ # Withdraw others for the same slot+day combination
264
+ for r in requests:
265
+ if (r["leave_id"] == target_req["leave_id"]
266
+ and r["affected_slot"]["period"] == target_req["affected_slot"]["period"]
267
+ and r["affected_slot"]["day"] == target_req["affected_slot"]["day"]
268
+ and r["request_id"] != request_id):
269
+ if r["status"] == "PENDING":
270
+ r["status"] = "WITHDRAWN"
271
+
272
+ save_substitution_requests(requests)
273
+
274
+ # Update Timetable
275
+ schedule_data = load_schedule()
276
+ if not schedule_data or not schedule_data.get("schedule"):
277
+ return True, "Substitution accepted but no schedule to update."
278
+
279
+ current_solution = schedule_data.get("schedule", {})
280
+ data = load_data()
281
+ faculties = data.get("faculties", [])
282
+
283
+ # Resolve faculty IDs reliably
284
+ original_fac_id = _resolve_faculty_id(target_req["original_faculty_id"], faculties) or target_req["original_faculty_id"]
285
+ sub_fac_id = _resolve_faculty_id(target_req["candidate_faculty_id"], faculties) or target_req["candidate_faculty_id"]
286
+
287
+ leave_fac_name = _get_faculty_name_by_id(original_fac_id, faculties)
288
+ sub_fac_name = _get_faculty_name_by_id(sub_fac_id, faculties)
289
+
290
+ day_idx = const.DAYS.index(target_req["affected_slot"]["day"]) if target_req["affected_slot"]["day"] in const.DAYS else -1
291
+
292
+ # The affected_slot section_id is a parent section (e.g., '6b').
293
+ # We must update ALL sub-sections (6b-E1, 6b-E2, etc.) that match.
294
+ parent_sec = target_req["affected_slot"]["section_id"]
295
+
296
+ updated = False
297
+ for task_id, info in current_solution.items():
298
+ faculty_name = info.get("faculty_name", "")
299
+ # Use ID-based matching instead of fragile name stripping
300
+ task_fac_id = _get_faculty_id_by_name(faculty_name, faculties)
301
+
302
+ # Match by parent section (e.g., task '6b-E1' matches parent '6b')
303
+ task_parent_sec = info.get("section_id", "").split("-")[0] if "-" in info.get("section_id", "") else info.get("section_id", "")
304
+
305
+ if (task_fac_id and task_fac_id == original_fac_id
306
+ and info.get("day_index") == day_idx
307
+ and task_parent_sec == parent_sec):
308
+ # Match period
309
+ duration = info.get("duration", 1)
310
+ start_p = info.get("period_index", 0)
311
+ if start_p <= target_req["affected_slot"]["period"] < start_p + duration:
312
+ # Update to the substitute teacher
313
+ info["faculty_name"] = sub_fac_name
314
+ info["is_substituted"] = True
315
+ info["original_faculty_name"] = leave_fac_name
316
+ updated = True
317
+ # DON'T break — update ALL sub-sections (E1, E2, etc.)
318
+
319
+ # If swap involved, swap the slots
320
+ if target_req.get("proposed_swap"):
321
+ swap = target_req["proposed_swap"]
322
+ # Not fully implemented atomic swap for brevity, but this is where it'd go
323
+ pass
324
+
325
+ # Save the updated schedule using the storage module (supports CommitScheduler)
326
+ save_schedule(current_solution)
327
+
328
+ add_history_entry(
329
+ operation_type="SUBSTITUTION",
330
+ description=f"Substituted {leave_fac_name} with {sub_fac_name} for {target_req['affected_slot']['subject_code']} on {target_req['affected_slot']['day']} P{target_req['affected_slot']['period']+1}",
331
+ affected_sections=[target_req['affected_slot'].get('section_id', '')],
332
+ changes=[],
333
+ status="SUCCESS",
334
+ )
335
+
336
+ return True, "Substitution accepted and timetable updated."
337
+
338
+ def handle_decline(request_id: str):
339
+ requests = load_substitution_requests()
340
+ target_req = next((r for r in requests if r["request_id"] == request_id), None)
341
+ if not target_req or target_req["status"] != "PENDING":
342
+ return False, "Request not found or not pending."
343
+
344
+ target_req["status"] = "DECLINED"
345
+ target_req["responded_at"] = datetime.now().isoformat()
346
+ save_substitution_requests(requests)
347
+
348
+ # Ideally, trigger find_substitutes_for_slot again here or move to next priority level.
349
+ # We would need to keep track of which priority level we are currently exploring for this slot.
350
+
351
+ return True, "Substitution declined."
352
+
353
+ def check_timeouts():
354
+ requests = load_substitution_requests()
355
+ changed = False
356
+ now = datetime.now()
357
+ for r in requests:
358
+ if r["status"] == "PENDING" and datetime.fromisoformat(r["expires_at"]) < now:
359
+ r["status"] = "TIMEOUT"
360
+ changed = True
361
+ if changed:
362
+ save_substitution_requests(requests)
substitution_requests.json ADDED
@@ -0,0 +1 @@
 
 
1
+ []
test_solver.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ import json
3
+ from models import Faculty, Subject, Section, Room, SubjectType
4
+ from data_loader import Allocation, prepare_scheduling_tasks
5
+ from solver import TimetableSolver
6
+ import constants as const
7
+
8
+ def get_subject_type_enum(type_str):
9
+ mapping = {
10
+ "THEORY": SubjectType.THEORY,
11
+ "LAB": SubjectType.LAB,
12
+ "SOFTSKILL": SubjectType.SOFTSKILL,
13
+ "FORUM": SubjectType.FORUM
14
+ }
15
+ return mapping.get(type_str.upper(), SubjectType.THEORY)
16
+
17
+ def convert_json_to_objects(data):
18
+ fac_objs = [Faculty(f['id'], f['name'], f['designation'], f['max_hours']) for f in data['faculties']]
19
+
20
+ sub_objs = [Subject(
21
+ s['code'], s['name'], s['credits'],
22
+ get_subject_type_enum(s['type']),
23
+ s.get('is_core', True), s.get('is_heavy', False)
24
+ ) for s in data['subjects']]
25
+
26
+ sec_objs = [Section(s['id'], s['semester'], s['strength']) for s in data['sections']]
27
+
28
+ room_objs = [Room(r['id'], r['capacity'], r['is_lab'], r['building']) for r in data['rooms']]
29
+
30
+ alloc_objs = [Allocation(
31
+ a['faculty_id'], a['subject_code'], a['section_id'], a.get('elective_group')
32
+ ) for a in data['allocations']]
33
+
34
+ return fac_objs, sub_objs, sec_objs, room_objs, alloc_objs
35
+
36
+ def main():
37
+ with open("timetable_data.json", "r") as f:
38
+ data = json.load(f)
39
+
40
+ facs, subs, secs, rooms, allocs = convert_json_to_objects(data)
41
+ tasks = prepare_scheduling_tasks(allocs, facs, subs, secs)
42
+
43
+ print(f"Scheduling {len(tasks)} tasks...")
44
+
45
+ solver = TimetableSolver(tasks, facs, secs, rooms)
46
+ status, solution = solver.solve(time_limit_seconds=30)
47
+
48
+ print(f"Status: {status}")
49
+ if status in ["OPTIMAL", "FEASIBLE"]:
50
+ print("Success!")
51
+ else:
52
+ print("Failed.")
53
+
54
+ if __name__ == "__main__":
55
+ main()
timetable_data.json ADDED
@@ -0,0 +1,1462 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "faculties": [
3
+ {
4
+ "id": "AI00355",
5
+ "name": "Dr. Vijayashekar S S",
6
+ "designation": "Professor",
7
+ "max_hours": 6
8
+ },
9
+ {
10
+ "id": "AI00378",
11
+ "name": "Dr. Kavitha Nair R",
12
+ "designation": "Assoc. Prof",
13
+ "max_hours": 20
14
+ },
15
+ {
16
+ "id": "AI00755",
17
+ "name": "Dr. S Anu Pallavi",
18
+ "designation": "Assoc. Prof",
19
+ "max_hours": 20
20
+ },
21
+ {
22
+ "id": "AI00425",
23
+ "name": "Mr. Jovin Deglus",
24
+ "designation": "Asst. Prof",
25
+ "max_hours": 20
26
+ },
27
+ {
28
+ "id": "AI00604",
29
+ "name": "Mr. Syed Musadiq Illahi",
30
+ "designation": "Asst. Prof",
31
+ "max_hours": 20
32
+ },
33
+ {
34
+ "id": "AI00893",
35
+ "name": "Mr. Nanda Kumar N",
36
+ "designation": "Asst. Prof",
37
+ "max_hours": 20
38
+ },
39
+ {
40
+ "id": "AI00822",
41
+ "name": "Ms. Surbhi",
42
+ "designation": "Asst. Prof",
43
+ "max_hours": 20
44
+ },
45
+ {
46
+ "id": "AI01146",
47
+ "name": "Ms. Vinutha M",
48
+ "designation": "Asst. Prof",
49
+ "max_hours": 20
50
+ },
51
+ {
52
+ "id": "AI00825",
53
+ "name": "Mr. Mhd Tahir Mirji",
54
+ "designation": "Asst. Prof",
55
+ "max_hours": 20
56
+ },
57
+ {
58
+ "id": "AI00917",
59
+ "name": "Mr. Sanjay P",
60
+ "designation": "Asst. Prof",
61
+ "max_hours": 20
62
+ },
63
+ {
64
+ "id": "AI01204",
65
+ "name": "Mr. Abhijith S",
66
+ "designation": "Asst. Prof",
67
+ "max_hours": 20
68
+ },
69
+ {
70
+ "id": "AI01093",
71
+ "name": "Mr. Praveen Arokia Raj",
72
+ "designation": "Asst. Prof",
73
+ "max_hours": 20
74
+ },
75
+ {
76
+ "id": "NTA1",
77
+ "name": "Nandita Tarikeri",
78
+ "designation": "TA",
79
+ "max_hours": 50
80
+ },
81
+ {
82
+ "id": "AI01515",
83
+ "name": "Mr. Ranjan Kumar",
84
+ "designation": "Asst. Prof",
85
+ "max_hours": 20
86
+ },
87
+ {
88
+ "id": "AI01514",
89
+ "name": "Mr. Chalasani Jayanth",
90
+ "designation": "Asst. Prof",
91
+ "max_hours": 20
92
+ },
93
+ {
94
+ "id": "NF3",
95
+ "name": "NF3",
96
+ "designation": "Asst. Prof",
97
+ "max_hours": 20
98
+ },
99
+ {
100
+ "id": "BASAV",
101
+ "name": "Basavaraj",
102
+ "designation": "Instructor",
103
+ "max_hours": 50
104
+ },
105
+ {
106
+ "id": "soft",
107
+ "name": "Soft Skills Trainer",
108
+ "designation": "Trainer",
109
+ "max_hours": 50
110
+ },
111
+ {
112
+ "id": "MBA1",
113
+ "name": "Mr. Yogesh Dixith",
114
+ "designation": "Asst. Prof",
115
+ "max_hours": 18
116
+ },
117
+ {
118
+ "id": "CIVIL",
119
+ "name": "CIVIL_1",
120
+ "designation": "Asst. Prof",
121
+ "max_hours": 18
122
+ },
123
+ {
124
+ "id": "MATHS",
125
+ "name": "Maths Department",
126
+ "designation": "External",
127
+ "max_hours": 18
128
+ },
129
+ {
130
+ "id": "oe_fac_1",
131
+ "name": "OE Teacher 1",
132
+ "designation": "Guest",
133
+ "max_hours": 18
134
+ },
135
+ {
136
+ "id": "oe_fac_2",
137
+ "name": "OE Teacher 2",
138
+ "designation": "Guest",
139
+ "max_hours": 18
140
+ },
141
+ {
142
+ "id": "oe_fac_3",
143
+ "name": "OE Teacher 3 (Sem 7)",
144
+ "designation": "Guest",
145
+ "max_hours": 18
146
+ },
147
+ {
148
+ "id": "MBA",
149
+ "name": "MBA Faculty",
150
+ "designation": "Asst. Prof",
151
+ "max_hours": 18
152
+ }
153
+ ],
154
+ "subjects": [
155
+ {
156
+ "code": "DL&RL",
157
+ "name": "DL& RL",
158
+ "type": "THEORY",
159
+ "credits": 3,
160
+ "is_core": true,
161
+ "is_heavy": true
162
+ },
163
+ {
164
+ "code": "DL&RL Lab",
165
+ "name": "DL&RL Lab",
166
+ "type": "FORUM",
167
+ "credits": 1,
168
+ "is_core": false,
169
+ "is_heavy": false
170
+ },
171
+ {
172
+ "code": "ML II",
173
+ "name": "Machine Learning-II",
174
+ "type": "THEORY",
175
+ "credits": 3,
176
+ "is_core": true,
177
+ "is_heavy": true
178
+ },
179
+ {
180
+ "code": "ML II Lab",
181
+ "name": "ML-II Lab",
182
+ "type": "FORUM",
183
+ "credits": 1,
184
+ "is_core": false,
185
+ "is_heavy": false
186
+ },
187
+ {
188
+ "code": "DS&P",
189
+ "name": "Data Security & Privacy",
190
+ "type": "THEORY",
191
+ "credits": 4,
192
+ "is_core": true,
193
+ "is_heavy": true
194
+ },
195
+ {
196
+ "code": "BA",
197
+ "name": "Business Analytics",
198
+ "type": "THEORY",
199
+ "credits": 3,
200
+ "is_core": false,
201
+ "is_heavy": false
202
+ },
203
+ {
204
+ "code": "BDA",
205
+ "name": "Big Data Analytics",
206
+ "type": "THEORY",
207
+ "credits": 3,
208
+ "is_core": false,
209
+ "is_heavy": false
210
+ },
211
+ {
212
+ "code": "OE (Sem 7)",
213
+ "name": "Open Elective (Sem 7)",
214
+ "type": "THEORY",
215
+ "credits": 3,
216
+ "is_core": false,
217
+ "is_heavy": false
218
+ },
219
+ {
220
+ "code": "PP",
221
+ "name": "Parallel Programming",
222
+ "type": "THEORY",
223
+ "credits": 3,
224
+ "is_core": true,
225
+ "is_heavy": true
226
+ },
227
+ {
228
+ "code": "PP Lab",
229
+ "name": "PP Lab",
230
+ "type": "FORUM",
231
+ "credits": 1,
232
+ "is_core": false,
233
+ "is_heavy": false
234
+ },
235
+ {
236
+ "code": "SML for DS",
237
+ "name": "Statistical ML for DS",
238
+ "type": "THEORY",
239
+ "credits": 3,
240
+ "is_core": true,
241
+ "is_heavy": true
242
+ },
243
+ {
244
+ "code": "SML for DS Lab",
245
+ "name": "Statistical ML for DS Lab",
246
+ "type": "FORUM",
247
+ "credits": 1,
248
+ "is_core": false,
249
+ "is_heavy": false
250
+ },
251
+ {
252
+ "code": "CNS",
253
+ "name": "Cryptography and Network Security",
254
+ "type": "THEORY",
255
+ "credits": 4,
256
+ "is_core": true,
257
+ "is_heavy": true
258
+ },
259
+ {
260
+ "code": "DL",
261
+ "name": "Deep Learning",
262
+ "type": "THEORY",
263
+ "credits": 3,
264
+ "is_core": false,
265
+ "is_heavy": false
266
+ },
267
+ {
268
+ "code": "SEPM",
269
+ "name": "Software Engg & Project Management",
270
+ "type": "THEORY",
271
+ "credits": 4,
272
+ "is_core": true,
273
+ "is_heavy": true
274
+ },
275
+ {
276
+ "code": "CN",
277
+ "name": "Computer Networks",
278
+ "type": "THEORY",
279
+ "credits": 3,
280
+ "is_core": true,
281
+ "is_heavy": true
282
+ },
283
+ {
284
+ "code": "CN Lab",
285
+ "name": "CN Lab",
286
+ "type": "FORUM",
287
+ "credits": 1,
288
+ "is_core": false,
289
+ "is_heavy": false
290
+ },
291
+ {
292
+ "code": "TOC",
293
+ "name": "Theory of Computation",
294
+ "type": "THEORY",
295
+ "credits": 5,
296
+ "is_core": true,
297
+ "is_heavy": true
298
+ },
299
+ {
300
+ "code": "DV Lab",
301
+ "name": "Data Visualization Lab",
302
+ "type": "LAB",
303
+ "credits": 1,
304
+ "is_core": true,
305
+ "is_heavy": true
306
+ },
307
+ {
308
+ "code": "RM&IPR",
309
+ "name": "Research Methodology & IPR",
310
+ "type": "THEORY",
311
+ "credits": 1,
312
+ "is_core": false,
313
+ "is_heavy": false
314
+ },
315
+ {
316
+ "code": "EVS",
317
+ "name": "Environmental Studies",
318
+ "type": "THEORY",
319
+ "credits": 2,
320
+ "is_core": false,
321
+ "is_heavy": false
322
+ },
323
+ {
324
+ "code": "CV",
325
+ "name": "Computer Vision",
326
+ "type": "THEORY",
327
+ "credits": 3,
328
+ "is_core": false,
329
+ "is_heavy": false
330
+ },
331
+ {
332
+ "code": "EDA",
333
+ "name": "Exploratory Data Analysis",
334
+ "type": "THEORY",
335
+ "credits": 3,
336
+ "is_core": false,
337
+ "is_heavy": false
338
+ },
339
+ {
340
+ "code": "NoSQL DB",
341
+ "name": "NoSQL Databases",
342
+ "type": "THEORY",
343
+ "credits": 3,
344
+ "is_core": false,
345
+ "is_heavy": false
346
+ },
347
+ {
348
+ "code": "DW",
349
+ "name": "Data Warehousing",
350
+ "type": "THEORY",
351
+ "credits": 3,
352
+ "is_core": false,
353
+ "is_heavy": false
354
+ },
355
+ {
356
+ "code": "Maths (Prob & Stats)",
357
+ "name": "Probability, Distributions and Statistics",
358
+ "type": "THEORY",
359
+ "credits": 5,
360
+ "is_core": true,
361
+ "is_heavy": true
362
+ },
363
+ {
364
+ "code": "DDCO",
365
+ "name": "Digital Design & Computer Organization",
366
+ "type": "THEORY",
367
+ "credits": 5,
368
+ "is_core": true,
369
+ "is_heavy": true
370
+ },
371
+ {
372
+ "code": "OS",
373
+ "name": "Operating Systems",
374
+ "type": "THEORY",
375
+ "credits": 3,
376
+ "is_core": true,
377
+ "is_heavy": true
378
+ },
379
+ {
380
+ "code": "DSA",
381
+ "name": "Data Structures and Applications",
382
+ "type": "THEORY",
383
+ "credits": 3,
384
+ "is_core": true,
385
+ "is_heavy": true
386
+ },
387
+ {
388
+ "code": "DSA Lab",
389
+ "name": "DSA Lab",
390
+ "type": "LAB",
391
+ "credits": 1,
392
+ "is_core": true,
393
+ "is_heavy": true
394
+ },
395
+ {
396
+ "code": "OOP with Java",
397
+ "name": "OOP with JAVA",
398
+ "type": "THEORY",
399
+ "credits": 1,
400
+ "is_core": true,
401
+ "is_heavy": true
402
+ },
403
+ {
404
+ "code": "EDA Lab",
405
+ "name": "Exploratory Data Analysis Lab",
406
+ "type": "FORUM",
407
+ "credits": 2,
408
+ "is_core": true,
409
+ "is_heavy": true
410
+ },
411
+ {
412
+ "code": "PM with Git",
413
+ "name": "Project Management with GIT Lab",
414
+ "type": "FORUM",
415
+ "credits": 2,
416
+ "is_core": true,
417
+ "is_heavy": true
418
+ },
419
+ {
420
+ "code": "Maths LE",
421
+ "name": "Mathematics for Lateral Entry Students",
422
+ "type": "THEORY",
423
+ "credits": 1,
424
+ "is_core": false,
425
+ "is_heavy": false
426
+ },
427
+ {
428
+ "code": "LIB_HR",
429
+ "name": "Library Hour",
430
+ "type": "THEORY",
431
+ "credits": 1,
432
+ "is_core": false,
433
+ "is_heavy": false
434
+ },
435
+ {
436
+ "code": "STU_HR",
437
+ "name": "Student Hour",
438
+ "type": "THEORY",
439
+ "credits": 1,
440
+ "is_core": false,
441
+ "is_heavy": false
442
+ },
443
+ {
444
+ "code": "FAC_HR",
445
+ "name": "Faculty Hour",
446
+ "type": "THEORY",
447
+ "credits": 1,
448
+ "is_core": false,
449
+ "is_heavy": false
450
+ },
451
+ {
452
+ "code": "STDY_HR",
453
+ "name": "Study Hour",
454
+ "type": "THEORY",
455
+ "credits": 1,
456
+ "is_core": false,
457
+ "is_heavy": false
458
+ },
459
+ {
460
+ "code": "Soft Skills",
461
+ "name": "Soft Skills",
462
+ "type": "SOFTSKILL",
463
+ "credits": 1,
464
+ "is_core": false,
465
+ "is_heavy": false
466
+ },
467
+ {
468
+ "code": "EDA Lab 2",
469
+ "name": "Exploratory Data Analysis Lab 2",
470
+ "type": "FORUM",
471
+ "credits": 2,
472
+ "is_core": true,
473
+ "is_heavy": true
474
+ },
475
+ {
476
+ "code": "PM with Git 2",
477
+ "name": "Project Management with GIT Lab 2",
478
+ "type": "FORUM",
479
+ "credits": 2,
480
+ "is_core": true,
481
+ "is_heavy": true
482
+ }
483
+ ],
484
+ "sections": [
485
+ {
486
+ "id": "7A",
487
+ "semester": 7,
488
+ "strength": 70
489
+ },
490
+ {
491
+ "id": "7B",
492
+ "semester": 7,
493
+ "strength": 70
494
+ },
495
+ {
496
+ "id": "5A",
497
+ "semester": 5,
498
+ "strength": 70
499
+ },
500
+ {
501
+ "id": "5B",
502
+ "semester": 5,
503
+ "strength": 70
504
+ },
505
+ {
506
+ "id": "3A",
507
+ "semester": 3,
508
+ "strength": 70
509
+ },
510
+ {
511
+ "id": "3B",
512
+ "semester": 3,
513
+ "strength": 70
514
+ },
515
+ {
516
+ "id": "7DS",
517
+ "semester": 7,
518
+ "strength": 70
519
+ },
520
+ {
521
+ "id": "5DS",
522
+ "semester": 5,
523
+ "strength": 70
524
+ },
525
+ {
526
+ "id": "3DS",
527
+ "semester": 3,
528
+ "strength": 70
529
+ },
530
+ {
531
+ "id": "OE_AI",
532
+ "semester": 7,
533
+ "strength": 70
534
+ },
535
+ {
536
+ "id": "7A-B1",
537
+ "semester": 7,
538
+ "strength": 35
539
+ },
540
+ {
541
+ "id": "7A-B2",
542
+ "semester": 7,
543
+ "strength": 35
544
+ },
545
+ {
546
+ "id": "7B-B1",
547
+ "semester": 7,
548
+ "strength": 35
549
+ },
550
+ {
551
+ "id": "7B-B2",
552
+ "semester": 7,
553
+ "strength": 35
554
+ },
555
+ {
556
+ "id": "7DS-B1",
557
+ "semester": 7,
558
+ "strength": 35
559
+ },
560
+ {
561
+ "id": "7DS-B2",
562
+ "semester": 7,
563
+ "strength": 35
564
+ },
565
+ {
566
+ "id": "5A-B1",
567
+ "semester": 5,
568
+ "strength": 35
569
+ },
570
+ {
571
+ "id": "5A-B2",
572
+ "semester": 5,
573
+ "strength": 35
574
+ },
575
+ {
576
+ "id": "5B-B1",
577
+ "semester": 5,
578
+ "strength": 35
579
+ },
580
+ {
581
+ "id": "5B-B2",
582
+ "semester": 5,
583
+ "strength": 35
584
+ },
585
+ {
586
+ "id": "5DS-B1",
587
+ "semester": 5,
588
+ "strength": 35
589
+ },
590
+ {
591
+ "id": "5DS-B2",
592
+ "semester": 5,
593
+ "strength": 35
594
+ },
595
+ {
596
+ "id": "3A-B1",
597
+ "semester": 3,
598
+ "strength": 35
599
+ },
600
+ {
601
+ "id": "3A-B2",
602
+ "semester": 3,
603
+ "strength": 35
604
+ },
605
+ {
606
+ "id": "3B-B1",
607
+ "semester": 3,
608
+ "strength": 35
609
+ },
610
+ {
611
+ "id": "3B-B2",
612
+ "semester": 3,
613
+ "strength": 35
614
+ },
615
+ {
616
+ "id": "3DS-B1",
617
+ "semester": 3,
618
+ "strength": 35
619
+ },
620
+ {
621
+ "id": "3DS-B2",
622
+ "semester": 3,
623
+ "strength": 35
624
+ }
625
+ ],
626
+ "rooms": [
627
+ {
628
+ "id": "AI09 3F 01",
629
+ "capacity": 75,
630
+ "is_lab": false,
631
+ "building": "ECE BLOCK"
632
+ },
633
+ {
634
+ "id": "AI09 3F 03",
635
+ "capacity": 75,
636
+ "is_lab": false,
637
+ "building": "ECE BLOCK"
638
+ },
639
+ {
640
+ "id": "AI09 3F 08",
641
+ "capacity": 75,
642
+ "is_lab": false,
643
+ "building": "ECE BLOCK"
644
+ },
645
+ {
646
+ "id": "AI09 2F 12",
647
+ "capacity": 75,
648
+ "is_lab": false,
649
+ "building": "ECE BLOCK"
650
+ },
651
+ {
652
+ "id": "AI09 2F 13",
653
+ "capacity": 75,
654
+ "is_lab": false,
655
+ "building": "ECE BLOCK"
656
+ },
657
+ {
658
+ "id": "AI09 2F 14",
659
+ "capacity": 75,
660
+ "is_lab": false,
661
+ "building": "ECE BLOCK"
662
+ },
663
+ {
664
+ "id": "AI09 2F 08",
665
+ "capacity": 75,
666
+ "is_lab": false,
667
+ "building": "ECE BLOCK"
668
+ },
669
+ {
670
+ "id": "AI09 1F 25",
671
+ "capacity": 75,
672
+ "is_lab": false,
673
+ "building": "ECE BLOCK"
674
+ },
675
+ {
676
+ "id": "AI09 1F26",
677
+ "capacity": 75,
678
+ "is_lab": false,
679
+ "building": "ECE BLOCK"
680
+ },
681
+ {
682
+ "id": "AI03 2F 01",
683
+ "capacity": 40,
684
+ "is_lab": true,
685
+ "building": "ASD BLOCK"
686
+ },
687
+ {
688
+ "id": "AI03 2F 02",
689
+ "capacity": 40,
690
+ "is_lab": true,
691
+ "building": "ASD BLOCK"
692
+ }
693
+ ],
694
+ "allocations": [
695
+ {
696
+ "faculty_id": "AI00355",
697
+ "subject_code": "DL&RL",
698
+ "section_id": "7A",
699
+ "elective_group": null
700
+ },
701
+ {
702
+ "faculty_id": "AI00355",
703
+ "subject_code": "DL&RL",
704
+ "section_id": "7B",
705
+ "elective_group": null
706
+ },
707
+ {
708
+ "faculty_id": "AI00378",
709
+ "subject_code": "DL&RL Lab",
710
+ "section_id": "7A-B1",
711
+ "elective_group": "lab_pair_7A_2"
712
+ },
713
+ {
714
+ "faculty_id": "AI00378",
715
+ "subject_code": "DL&RL Lab",
716
+ "section_id": "7A-B2",
717
+ "elective_group": "lab_pair_7A_1"
718
+ },
719
+ {
720
+ "faculty_id": "AI01093",
721
+ "subject_code": "DL&RL Lab",
722
+ "section_id": "7B-B1",
723
+ "elective_group": "lab_pair_7B_2"
724
+ },
725
+ {
726
+ "faculty_id": "AI01093",
727
+ "subject_code": "DL&RL Lab",
728
+ "section_id": "7B-B2",
729
+ "elective_group": "lab_pair_7B_1"
730
+ },
731
+ {
732
+ "faculty_id": "AI00604",
733
+ "subject_code": "ML II",
734
+ "section_id": "7A",
735
+ "elective_group": null
736
+ },
737
+ {
738
+ "faculty_id": "AI01204",
739
+ "subject_code": "ML II",
740
+ "section_id": "7B",
741
+ "elective_group": null
742
+ },
743
+ {
744
+ "faculty_id": "AI00604",
745
+ "subject_code": "ML II Lab",
746
+ "section_id": "7A-B1",
747
+ "elective_group": "lab_pair_7A_1"
748
+ },
749
+ {
750
+ "faculty_id": "AI00604",
751
+ "subject_code": "ML II Lab",
752
+ "section_id": "7A-B2",
753
+ "elective_group": "lab_pair_7A_2"
754
+ },
755
+ {
756
+ "faculty_id": "AI01204",
757
+ "subject_code": "ML II Lab",
758
+ "section_id": "7B-B1",
759
+ "elective_group": "lab_pair_7B_1"
760
+ },
761
+ {
762
+ "faculty_id": "AI01204",
763
+ "subject_code": "ML II Lab",
764
+ "section_id": "7B-B2",
765
+ "elective_group": "lab_pair_7B_2"
766
+ },
767
+ {
768
+ "faculty_id": "AI00425",
769
+ "subject_code": "DS&P",
770
+ "section_id": "7A",
771
+ "elective_group": null
772
+ },
773
+ {
774
+ "faculty_id": "AI00425",
775
+ "subject_code": "DS&P",
776
+ "section_id": "7B",
777
+ "elective_group": null
778
+ },
779
+ {
780
+ "faculty_id": "MBA",
781
+ "subject_code": "BA",
782
+ "section_id": "7A",
783
+ "elective_group": "ELEC_7"
784
+ },
785
+ {
786
+ "faculty_id": "MBA",
787
+ "subject_code": "BA",
788
+ "section_id": "7B",
789
+ "elective_group": "ELEC_7"
790
+ },
791
+ {
792
+ "faculty_id": "MBA1",
793
+ "subject_code": "BA",
794
+ "section_id": "7DS",
795
+ "elective_group": "ELEC_7"
796
+ },
797
+ {
798
+ "faculty_id": "AI00822",
799
+ "subject_code": "BDA",
800
+ "section_id": "7A",
801
+ "elective_group": "ELEC_7"
802
+ },
803
+ {
804
+ "faculty_id": "AI00822",
805
+ "subject_code": "BDA",
806
+ "section_id": "7B",
807
+ "elective_group": "ELEC_7"
808
+ },
809
+ {
810
+ "faculty_id": "AI00893",
811
+ "subject_code": "DL",
812
+ "section_id": "7DS",
813
+ "elective_group": "ELEC_7"
814
+ },
815
+ {
816
+ "faculty_id": "oe_fac_1",
817
+ "subject_code": "OE (Sem 7)",
818
+ "section_id": "7A",
819
+ "elective_group": null
820
+ },
821
+ {
822
+ "faculty_id": "oe_fac_2",
823
+ "subject_code": "OE (Sem 7)",
824
+ "section_id": "7B",
825
+ "elective_group": null
826
+ },
827
+ {
828
+ "faculty_id": "oe_fac_3",
829
+ "subject_code": "OE (Sem 7)",
830
+ "section_id": "7DS",
831
+ "elective_group": null
832
+ },
833
+ {
834
+ "faculty_id": "AI01514",
835
+ "subject_code": "PP",
836
+ "section_id": "7DS",
837
+ "elective_group": null
838
+ },
839
+ {
840
+ "faculty_id": "AI01514",
841
+ "subject_code": "PP Lab",
842
+ "section_id": "7DS-B1",
843
+ "elective_group": null
844
+ },
845
+ {
846
+ "faculty_id": "AI01514",
847
+ "subject_code": "PP Lab",
848
+ "section_id": "7DS-B2",
849
+ "elective_group": null
850
+ },
851
+ {
852
+ "faculty_id": "AI00378",
853
+ "subject_code": "SML for DS",
854
+ "section_id": "7DS",
855
+ "elective_group": null
856
+ },
857
+ {
858
+ "faculty_id": "AI00378",
859
+ "subject_code": "SML for DS Lab",
860
+ "section_id": "7DS-B1",
861
+ "elective_group": null
862
+ },
863
+ {
864
+ "faculty_id": "AI00378",
865
+ "subject_code": "SML for DS Lab",
866
+ "section_id": "7DS-B2",
867
+ "elective_group": null
868
+ },
869
+ {
870
+ "faculty_id": "AI00425",
871
+ "subject_code": "CNS",
872
+ "section_id": "7DS",
873
+ "elective_group": null
874
+ },
875
+ {
876
+ "faculty_id": "NF3",
877
+ "subject_code": "SEPM",
878
+ "section_id": "5A",
879
+ "elective_group": null
880
+ },
881
+ {
882
+ "faculty_id": "NF3",
883
+ "subject_code": "SEPM",
884
+ "section_id": "5B",
885
+ "elective_group": null
886
+ },
887
+ {
888
+ "faculty_id": "AI00755",
889
+ "subject_code": "CN",
890
+ "section_id": "5A",
891
+ "elective_group": null
892
+ },
893
+ {
894
+ "faculty_id": "AI00755",
895
+ "subject_code": "CN",
896
+ "section_id": "5B",
897
+ "elective_group": null
898
+ },
899
+ {
900
+ "faculty_id": "AI00755",
901
+ "subject_code": "CN Lab",
902
+ "section_id": "5A-B1",
903
+ "elective_group": null
904
+ },
905
+ {
906
+ "faculty_id": "AI00755",
907
+ "subject_code": "CN Lab",
908
+ "section_id": "5A-B2",
909
+ "elective_group": null
910
+ },
911
+ {
912
+ "faculty_id": "AI00755",
913
+ "subject_code": "CN Lab",
914
+ "section_id": "5B-B1",
915
+ "elective_group": null
916
+ },
917
+ {
918
+ "faculty_id": "AI00755",
919
+ "subject_code": "CN Lab",
920
+ "section_id": "5B-B2",
921
+ "elective_group": null
922
+ },
923
+ {
924
+ "faculty_id": "AI01146",
925
+ "subject_code": "TOC",
926
+ "section_id": "5A",
927
+ "elective_group": null
928
+ },
929
+ {
930
+ "faculty_id": "AI01146",
931
+ "subject_code": "TOC",
932
+ "section_id": "5B",
933
+ "elective_group": null
934
+ },
935
+ {
936
+ "faculty_id": "AI00425",
937
+ "subject_code": "DV Lab",
938
+ "section_id": "5B-B1",
939
+ "elective_group": null
940
+ },
941
+ {
942
+ "faculty_id": "AI00425",
943
+ "subject_code": "DV Lab",
944
+ "section_id": "5B-B2",
945
+ "elective_group": null
946
+ },
947
+ {
948
+ "faculty_id": "AI00604",
949
+ "subject_code": "CV",
950
+ "section_id": "5A",
951
+ "elective_group": "ELEC_5_AIML"
952
+ },
953
+ {
954
+ "faculty_id": "AI00822",
955
+ "subject_code": "EDA",
956
+ "section_id": "5A",
957
+ "elective_group": "ELEC_5_AIML"
958
+ },
959
+ {
960
+ "faculty_id": "AI00604",
961
+ "subject_code": "CV",
962
+ "section_id": "5B",
963
+ "elective_group": "ELEC_5_AIML"
964
+ },
965
+ {
966
+ "faculty_id": "AI00822",
967
+ "subject_code": "EDA",
968
+ "section_id": "5B",
969
+ "elective_group": "ELEC_5_AIML"
970
+ },
971
+ {
972
+ "faculty_id": "BASAV",
973
+ "subject_code": "RM&IPR",
974
+ "section_id": "5A",
975
+ "elective_group": null
976
+ },
977
+ {
978
+ "faculty_id": "BASAV",
979
+ "subject_code": "RM&IPR",
980
+ "section_id": "5B",
981
+ "elective_group": null
982
+ },
983
+ {
984
+ "faculty_id": "CIVIL",
985
+ "subject_code": "EVS",
986
+ "section_id": "5A",
987
+ "elective_group": null
988
+ },
989
+ {
990
+ "faculty_id": "CIVIL",
991
+ "subject_code": "EVS",
992
+ "section_id": "5B",
993
+ "elective_group": null
994
+ },
995
+ {
996
+ "faculty_id": "AI01515",
997
+ "subject_code": "SEPM",
998
+ "section_id": "5DS",
999
+ "elective_group": null
1000
+ },
1001
+ {
1002
+ "faculty_id": "AI00893",
1003
+ "subject_code": "CN",
1004
+ "section_id": "5DS",
1005
+ "elective_group": null
1006
+ },
1007
+ {
1008
+ "faculty_id": "AI00893",
1009
+ "subject_code": "CN Lab",
1010
+ "section_id": "5DS-B1",
1011
+ "elective_group": null
1012
+ },
1013
+ {
1014
+ "faculty_id": "AI00893",
1015
+ "subject_code": "CN Lab",
1016
+ "section_id": "5DS-B2",
1017
+ "elective_group": null
1018
+ },
1019
+ {
1020
+ "faculty_id": "AI01146",
1021
+ "subject_code": "TOC",
1022
+ "section_id": "5DS",
1023
+ "elective_group": null
1024
+ },
1025
+ {
1026
+ "faculty_id": "AI00604",
1027
+ "subject_code": "DV Lab",
1028
+ "section_id": "5DS-B1",
1029
+ "elective_group": null
1030
+ },
1031
+ {
1032
+ "faculty_id": "AI01093",
1033
+ "subject_code": "DV Lab",
1034
+ "section_id": "5DS-B2",
1035
+ "elective_group": null
1036
+ },
1037
+ {
1038
+ "faculty_id": "AI00825",
1039
+ "subject_code": "NoSQL DB",
1040
+ "section_id": "5DS",
1041
+ "elective_group": "ELEC_5_DS"
1042
+ },
1043
+ {
1044
+ "faculty_id": "AI01204",
1045
+ "subject_code": "DW",
1046
+ "section_id": "5DS",
1047
+ "elective_group": "ELEC_5_DS"
1048
+ },
1049
+ {
1050
+ "faculty_id": "BASAV",
1051
+ "subject_code": "RM&IPR",
1052
+ "section_id": "5DS",
1053
+ "elective_group": null
1054
+ },
1055
+ {
1056
+ "faculty_id": "CIVIL",
1057
+ "subject_code": "EVS",
1058
+ "section_id": "5DS",
1059
+ "elective_group": null
1060
+ },
1061
+ {
1062
+ "faculty_id": "MATHS",
1063
+ "subject_code": "Maths (Prob & Stats)",
1064
+ "section_id": "3A",
1065
+ "elective_group": null
1066
+ },
1067
+ {
1068
+ "faculty_id": "MATHS",
1069
+ "subject_code": "Maths (Prob & Stats)",
1070
+ "section_id": "3B",
1071
+ "elective_group": null
1072
+ },
1073
+ {
1074
+ "faculty_id": "AI01515",
1075
+ "subject_code": "DDCO",
1076
+ "section_id": "3A",
1077
+ "elective_group": null
1078
+ },
1079
+ {
1080
+ "faculty_id": "AI01515",
1081
+ "subject_code": "DDCO",
1082
+ "section_id": "3B",
1083
+ "elective_group": null
1084
+ },
1085
+ {
1086
+ "faculty_id": "AI00917",
1087
+ "subject_code": "OS",
1088
+ "section_id": "3A",
1089
+ "elective_group": null
1090
+ },
1091
+ {
1092
+ "faculty_id": "AI01146",
1093
+ "subject_code": "OS",
1094
+ "section_id": "3B",
1095
+ "elective_group": null
1096
+ },
1097
+ {
1098
+ "faculty_id": "AI00822",
1099
+ "subject_code": "DSA",
1100
+ "section_id": "3A",
1101
+ "elective_group": null
1102
+ },
1103
+ {
1104
+ "faculty_id": "AI00825",
1105
+ "subject_code": "DSA",
1106
+ "section_id": "3B",
1107
+ "elective_group": null
1108
+ },
1109
+ {
1110
+ "faculty_id": "AI00822",
1111
+ "subject_code": "DSA Lab",
1112
+ "section_id": "3A-B1",
1113
+ "elective_group": null
1114
+ },
1115
+ {
1116
+ "faculty_id": "AI00893",
1117
+ "subject_code": "DSA Lab",
1118
+ "section_id": "3A-B2",
1119
+ "elective_group": null
1120
+ },
1121
+ {
1122
+ "faculty_id": "AI00822",
1123
+ "subject_code": "DSA Lab",
1124
+ "section_id": "3B-B1",
1125
+ "elective_group": null
1126
+ },
1127
+ {
1128
+ "faculty_id": "AI00893",
1129
+ "subject_code": "DSA Lab",
1130
+ "section_id": "3A-B1",
1131
+ "elective_group": null
1132
+ },
1133
+ {
1134
+ "faculty_id": "NTA1",
1135
+ "subject_code": "OOP with Java",
1136
+ "section_id": "3A",
1137
+ "elective_group": null
1138
+ },
1139
+ {
1140
+ "faculty_id": "NTA1",
1141
+ "subject_code": "OOP with Java",
1142
+ "section_id": "3B",
1143
+ "elective_group": null
1144
+ },
1145
+ {
1146
+ "faculty_id": "AI00378",
1147
+ "subject_code": "EDA Lab",
1148
+ "section_id": "3A",
1149
+ "elective_group": "ELEC_3_LAB"
1150
+ },
1151
+ {
1152
+ "faculty_id": "AI00378",
1153
+ "subject_code": "EDA Lab",
1154
+ "section_id": "3B",
1155
+ "elective_group": "ELEC_3_LAB"
1156
+ },
1157
+ {
1158
+ "faculty_id": "AI00917",
1159
+ "subject_code": "PM with Git",
1160
+ "section_id": "3A",
1161
+ "elective_group": "ELEC_3_LAB"
1162
+ },
1163
+ {
1164
+ "faculty_id": "AI00917",
1165
+ "subject_code": "PM with Git",
1166
+ "section_id": "3B",
1167
+ "elective_group": "ELEC_3_LAB"
1168
+ },
1169
+ {
1170
+ "faculty_id": "MATHS",
1171
+ "subject_code": "Maths LE",
1172
+ "section_id": "3A",
1173
+ "elective_group": null
1174
+ },
1175
+ {
1176
+ "faculty_id": "MATHS",
1177
+ "subject_code": "Maths LE",
1178
+ "section_id": "3B",
1179
+ "elective_group": null
1180
+ },
1181
+ {
1182
+ "faculty_id": "MATHS",
1183
+ "subject_code": "Maths (Prob & Stats)",
1184
+ "section_id": "3DS",
1185
+ "elective_group": null
1186
+ },
1187
+ {
1188
+ "faculty_id": "AI01514",
1189
+ "subject_code": "DDCO",
1190
+ "section_id": "3DS",
1191
+ "elective_group": null
1192
+ },
1193
+ {
1194
+ "faculty_id": "AI00917",
1195
+ "subject_code": "OS",
1196
+ "section_id": "3DS",
1197
+ "elective_group": null
1198
+ },
1199
+ {
1200
+ "faculty_id": "AI00825",
1201
+ "subject_code": "DSA",
1202
+ "section_id": "3DS",
1203
+ "elective_group": null
1204
+ },
1205
+ {
1206
+ "faculty_id": "AI00893",
1207
+ "subject_code": "DSA Lab",
1208
+ "section_id": "3DS-B1",
1209
+ "elective_group": null
1210
+ },
1211
+ {
1212
+ "faculty_id": "AI00917",
1213
+ "subject_code": "DSA Lab",
1214
+ "section_id": "3DS-B1",
1215
+ "elective_group": null
1216
+ },
1217
+ {
1218
+ "faculty_id": "NTA1",
1219
+ "subject_code": "OOP with Java",
1220
+ "section_id": "3DS",
1221
+ "elective_group": null
1222
+ },
1223
+ {
1224
+ "faculty_id": "AI00378",
1225
+ "subject_code": "EDA Lab",
1226
+ "section_id": "3DS",
1227
+ "elective_group": "ELEC_3_LAB"
1228
+ },
1229
+ {
1230
+ "faculty_id": "MATHS",
1231
+ "subject_code": "Maths LE",
1232
+ "section_id": "3DS",
1233
+ "elective_group": null
1234
+ },
1235
+ {
1236
+ "faculty_id": "NTA1",
1237
+ "subject_code": "DL&RL",
1238
+ "section_id": "7A",
1239
+ "elective_group": null
1240
+ },
1241
+ {
1242
+ "faculty_id": "NTA1",
1243
+ "subject_code": "DL&RL",
1244
+ "section_id": "7B",
1245
+ "elective_group": null
1246
+ },
1247
+ {
1248
+ "faculty_id": "AI00755",
1249
+ "subject_code": "DV Lab",
1250
+ "section_id": "5A-B1",
1251
+ "elective_group": null
1252
+ },
1253
+ {
1254
+ "faculty_id": "AI00425",
1255
+ "subject_code": "DV Lab",
1256
+ "section_id": "5A-B1",
1257
+ "elective_group": null
1258
+ },
1259
+ {
1260
+ "faculty_id": "AI01204",
1261
+ "subject_code": "DV Lab",
1262
+ "section_id": "5A-B2",
1263
+ "elective_group": null
1264
+ },
1265
+ {
1266
+ "faculty_id": "AI01514",
1267
+ "subject_code": "DV Lab",
1268
+ "section_id": "5A-B2",
1269
+ "elective_group": null
1270
+ },
1271
+ {
1272
+ "faculty_id": "AI00825",
1273
+ "subject_code": "DSA Lab",
1274
+ "section_id": "3B-B1",
1275
+ "elective_group": null
1276
+ },
1277
+ {
1278
+ "faculty_id": "AI00825",
1279
+ "subject_code": "DSA Lab",
1280
+ "section_id": "3B-B2",
1281
+ "elective_group": null
1282
+ },
1283
+ {
1284
+ "faculty_id": "AI00825",
1285
+ "subject_code": "DSA Lab",
1286
+ "section_id": "3DS-B2",
1287
+ "elective_group": null
1288
+ },
1289
+ {
1290
+ "faculty_id": "AI00822",
1291
+ "subject_code": "DSA Lab",
1292
+ "section_id": "3A-B2",
1293
+ "elective_group": null
1294
+ },
1295
+ {
1296
+ "faculty_id": "AI00822",
1297
+ "subject_code": "DSA Lab",
1298
+ "section_id": "3B-B2",
1299
+ "elective_group": null
1300
+ },
1301
+ {
1302
+ "faculty_id": "AI01515",
1303
+ "subject_code": "DV Lab",
1304
+ "section_id": "5B-B1",
1305
+ "elective_group": null
1306
+ },
1307
+ {
1308
+ "faculty_id": "AI01515",
1309
+ "subject_code": "DV Lab",
1310
+ "section_id": "5B-B2",
1311
+ "elective_group": null
1312
+ },
1313
+ {
1314
+ "faculty_id": "AI01514",
1315
+ "subject_code": "DV Lab",
1316
+ "section_id": "5DS-B1",
1317
+ "elective_group": null
1318
+ },
1319
+ {
1320
+ "faculty_id": "AI00917",
1321
+ "subject_code": "PM with Git",
1322
+ "section_id": "3DS",
1323
+ "elective_group": "ELEC_3_LAB"
1324
+ },
1325
+ {
1326
+ "faculty_id": "AI00893",
1327
+ "subject_code": "DSA Lab",
1328
+ "section_id": "3DS-B2",
1329
+ "elective_group": null
1330
+ },
1331
+ {
1332
+ "faculty_id": "AI01514",
1333
+ "subject_code": "DV Lab",
1334
+ "section_id": "5DS-B2",
1335
+ "elective_group": null
1336
+ },
1337
+ {
1338
+ "faculty_id": "AI00378",
1339
+ "subject_code": "EDA Lab 2",
1340
+ "section_id": "3A",
1341
+ "elective_group": "ELEC_3_LAB_2"
1342
+ },
1343
+ {
1344
+ "faculty_id": "AI00378",
1345
+ "subject_code": "EDA Lab 2",
1346
+ "section_id": "3B",
1347
+ "elective_group": "ELEC_3_LAB_2"
1348
+ },
1349
+ {
1350
+ "faculty_id": "AI00378",
1351
+ "subject_code": "EDA Lab 2",
1352
+ "section_id": "3DS",
1353
+ "elective_group": "ELEC_3_LAB_2"
1354
+ },
1355
+ {
1356
+ "faculty_id": "AI00917",
1357
+ "subject_code": "PM with Git 2",
1358
+ "section_id": "3A",
1359
+ "elective_group": "ELEC_3_LAB_2"
1360
+ },
1361
+ {
1362
+ "faculty_id": "AI00917",
1363
+ "subject_code": "PM with Git 2",
1364
+ "section_id": "3B",
1365
+ "elective_group": "ELEC_3_LAB_2"
1366
+ },
1367
+ {
1368
+ "faculty_id": "AI00917",
1369
+ "subject_code": "PM with Git 2",
1370
+ "section_id": "3DS",
1371
+ "elective_group": "ELEC_3_LAB_2"
1372
+ }
1373
+ ],
1374
+ "scheduling_rules": [
1375
+ {
1376
+ "id": "50480578-d82b-4f28-9ebf-416f46e9773b",
1377
+ "rule_type": "FIXED_PERIOD",
1378
+ "subject_codes": [
1379
+ "OE (Sem 7)"
1380
+ ],
1381
+ "subject_types": [],
1382
+ "period_index": 2
1383
+ },
1384
+ {
1385
+ "id": "5f6a4afd-9fd4-4930-835e-c8a387bb8c76",
1386
+ "rule_type": "FIXED_DAYS",
1387
+ "subject_codes": [
1388
+ "OE (Sem 7)"
1389
+ ],
1390
+ "subject_types": [],
1391
+ "days": [
1392
+ "MON",
1393
+ "TUE",
1394
+ "WED"
1395
+ ]
1396
+ },
1397
+ {
1398
+ "id": "bc2547c3-1511-46ae-a71b-580d576e2633",
1399
+ "rule_type": "FACULTY_UNAVAILABLE",
1400
+ "faculty_id": "AI00822",
1401
+ "subject_codes": [],
1402
+ "subject_types": [],
1403
+ "period_index": 0,
1404
+ "days": [
1405
+ "MON",
1406
+ "TUE",
1407
+ "WED",
1408
+ "THU",
1409
+ "FRI"
1410
+ ]
1411
+ },
1412
+ {
1413
+ "id": "eb002c24-3ebd-460b-9571-6bf21163cfcc",
1414
+ "rule_type": "FACULTY_UNAVAILABLE",
1415
+ "faculty_id": "AI00604",
1416
+ "subject_codes": [],
1417
+ "subject_types": [],
1418
+ "period_index": 2,
1419
+ "days": [
1420
+ "MON",
1421
+ "TUE",
1422
+ "WED"
1423
+ ]
1424
+ },
1425
+ {
1426
+ "id": "ba936692-8887-440e-ab6a-a95cce177241",
1427
+ "rule_type": "FACULTY_UNAVAILABLE",
1428
+ "faculty_id": "AI00604",
1429
+ "subject_codes": [],
1430
+ "subject_types": [],
1431
+ "period_index": 1,
1432
+ "days": [
1433
+ "WED",
1434
+ "FRI"
1435
+ ]
1436
+ },
1437
+ {
1438
+ "id": "48c1d6b3-4002-4d97-afa1-1964a7be668e",
1439
+ "rule_type": "FACULTY_UNAVAILABLE",
1440
+ "faculty_id": "AI00604",
1441
+ "subject_codes": [],
1442
+ "subject_types": [],
1443
+ "period_index": 2,
1444
+ "days": [
1445
+ "THU"
1446
+ ]
1447
+ },
1448
+ {
1449
+ "id": "a44c5fdc-c6a0-4f8c-bcfa-dd1375ad7eb0",
1450
+ "rule_type": "FACULTY_UNAVAILABLE",
1451
+ "faculty_id": "AI00825",
1452
+ "subject_codes": [],
1453
+ "subject_types": [],
1454
+ "period_index": 2,
1455
+ "days": [
1456
+ "MON",
1457
+ "TUE",
1458
+ "WED"
1459
+ ]
1460
+ }
1461
+ ]
1462
+ }