Spaces:
Sleeping
Sleeping
Commit ·
2da1ccd
0
Parent(s):
Super-squash branch 'main' using huggingface_hub
Browse files- .gitattributes +35 -0
- Dockerfile +18 -0
- Procfile +1 -0
- README.md +10 -0
- api.py +1866 -0
- app.py +639 -0
- cancellations.json +24 -0
- change_history.json +0 -0
- constants.py +30 -0
- constraint_engine.py +377 -0
- data.json +0 -0
- data_loader.py +232 -0
- debug_counts.py +37 -0
- debug_display.py +95 -0
- leave_requests.json +1 -0
- main.py +254 -0
- master_dataset.json +0 -0
- merge_datasets.py +75 -0
- merged_dataset.json +0 -0
- models.py +145 -0
- objective_engine.py +407 -0
- original_schedule.json +2597 -0
- partial_optimizer.py +1126 -0
- pending_timetables.json +0 -0
- reoptimizer.py +238 -0
- requirements.txt +11 -0
- saved_schedule.json +2585 -0
- schedule_versions.json +0 -0
- slm_inference.py +384 -0
- solver.py +149 -0
- storage.py +273 -0
- substitution_engine.py +362 -0
- substitution_requests.json +1 -0
- test_solver.py +55 -0
- timetable_data.json +1462 -0
.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 |
+
}
|