A Streamlit app that builds a fair lunch duty schedule for a school year (or any month or date range) from two CSVs: the school calendar and staff availability. The schedule is optimized as a whole with Google OR-Tools.
- 🎯 Fair Distribution: Everyone gets equal duties (within ±1)
- 📅 Smart Scheduling: Respects Mon/Tue/Wed availability
- 🔒 Constraints: Max 1 duty per person per week
- 🏠 Quiet Room Rotation: Fair distribution of quiet room assignments
- 📊 Analytics: View duty counts and distribution stats
- 💾 Export: Download schedules as CSV
pip install -r requirements.txt
streamlit run lunch_duty_scheduler_app_02.py- Fork this repository
- Go to share.streamlit.io
- Connect your GitHub repo
- Deploy!
If you edit the CSV this app exports by hand, csv_to_pdf/ turns it into a printable PDF:
pip install -r csv_to_pdf/requirements.txt
streamlit run csv_to_pdf/csv_to_pdf_app.pyUpload these CSV files in the app:
- Calendar CSV - School calendar with duty days marked
- Staff Availability CSV - Staff names and Mon/Tue/Wed availability
Optimizer (V3, default): the whole period is one cost-minimization problem (an integer program), solved by
Google OR-Tools' CP-SAT solver (optimizer.py). Each possible schedule has a total cost, and the solver searches for
the cheapest one, starting from the greedy schedule (and never returning anything worse than it).
- Never broken: nobody works a day they marked unavailable; 3 staff per day; one quiet-room person per day.
- Costs, most to least important: an empty slot (100,000) → a second duty in one week (5,000) → each duty outside someone's fair range (2,000; the fair range is an even share, lowered for people whose availability can't reach it) → two "avoid pairing" staff on the same day (400) → duties close together (up to 60, fading to 0 at the ideal gap) → a "try to pair" person without a partner (40) → quiet-room and weekday balance (20 / 10).
- The app reports the cost, the greedy schedule's cost for comparison, whether the result is proven optimal, and what
is still not met. Weights live in
optimizer.Weights.
Quick greedy (V2): greedy.py, kept as an option and as the optimizer's starting point: it fills one day at a
time with the 3 highest-scoring eligible people (quota urgency, gap since last duty, weekday variety), then does one
pass of swaps for the pairing tags.
Tests: python -m pytest tests (made-up staff and calendar; about a minute).
- Streamlit - Web framework
- Pandas - Data manipulation
- NumPy - Numerical computing
- OR-Tools - CP-SAT solver
MIT License - feel free to adapt for your school!