Feat/room blockout - #105
Conversation
Room blockout CSV support:
- RoomBlockoutSchema and RoomBlockoutAdapter with flexible day/block
parsing (accepts integers or named values like 'Monday', '9AM-11AM')
- room_blockouts field on SchedulingDataset; DatasetFactory accepts
optional blockouts_df
- _assign_rooms skips blocked rooms and tracks unroomed courses in
ScheduleResult.unassigned so unplaced_exams count is accurate
- API route and DatasetService accept optional room_blockouts file
- Precompute blockout_slots into file_paths JSONB metadata at upload
time so schedule retrieval never re-reads from S3
- Frontend: optional upload slot with badge, store, and API support
- Tests: adapter parsing, scheduler enforcement, end-to-end pipeline
Merge group scheduling fix:
- Replace weight-9999 edge hack with DSATUR graph contraction — edges
mean must-be-different-color so the old approach prevented merged
courses from sharing a slot rather than guaranteeing it
- Contract each merge group into a virtual node, union external edges,
expand colors back after coloring
- Sum soft-constraint penalties across all CRNs in a group during slot
selection so back-to-back and load costs reflect the full group
- Add algorithm correctness test suite (coloring validity, no hard
conflicts, merge invariants, determinism)
Other:
- Run scheduler in asyncio.to_thread (CPU-bound work was blocking the
event loop)
- Make instructor, department, and term optional in course schema so
datasets missing these fields upload successfully
- Pass per-slot blocked room counts through schedule API response
(blockouts field on both generate and retrieve endpoints)
- CompactView shows blocked room badge per calendar cell
- DensityView shows blocked room badge alongside conflict badges
- StatisticsView shows blockout stats card (rooms constrained, total
blocked slots) when blockouts are present
- Extract extractTimeFromBlock to shared utils.ts, removing duplicate
implementations in both calendar views
…counts
Generate 500'd on any over-capacity merge group: _build_generation_response
received merges=None (caller omitted it) and called merges.get() in the
unscheduled-merge loop. The summary counts also diverged from the retrieve
path -- generate counted only unroomed exams (len(result.unassigned)) while
retrieve counted unroomed + null-slot unscheduled-merge rows.
- Pass merges into _build_generation_response (+ defensive merges = merges or {})
- Add _summarize_placement() computing num_classes/unplaced_exams to match the
row-based definition in _calculate_summary_stats
- Add regression tests incl. the generate==retrieve invariant
Pre-existing on develop; fixed here because feat/room-blockout's spec requires
accurate unplaced_exams and surfacing of unscheduled exams.
Refs EXENG-14. num_students parity tracked in EXENG-15.
|
Pushed a standalone fix commit (
Verified live: generate now 200; generate and retrieve both report Pre-existing on |
|
@coderabbitai review |
Summary
Room blockout CSV support:
parsing (accepts integers or named values like 'Monday', '9AM-11AM')
optional blockouts_df
ScheduleResult.unassigned so unplaced_exams count is accurate
time so schedule retrieval never re-reads from S3
Merge group scheduling fix:
mean must-be-different-color so the old approach prevented merged
courses from sharing a slot rather than guaranteeing it
expand colors back after coloring
selection so back-to-back and load costs reflect the full group
conflicts, merge invariants, determinism)
Frontend:
(blockouts field on both generate and retrieve endpoints)
blocked slots) when blockouts are present
implementations in both calendar views
Other:
event loop)
datasets missing these fields upload successfully