Shift Optimizer
prototypeA quick-service restaurant runs a 23-hour business day with a lunch rush, a dead mid-afternoon, and a skeleton night crew — and most managers cover all three by gut feel.
The problem
I had a working scheduler for exactly this, but it was buried. It lived in a 2,000-line Jupyter notebook where a simple greedy algorithm sat under config classes, abstract data-source interfaces, and a heavyweight matplotlib animator. It ran once, on my machine. Nobody else could read it — let alone trust the schedule it produced.
Why it mattered
A scheduling tool nobody can read is a scheduling tool nobody will use. As a portfolio piece it was worse than nothing: 2,000 lines of ceremony reads as over-engineering, not judgment. The one thing that actually mattered — the coverage logic — was invisible under the scaffolding.
What I built
I stripped it back to the idea: a ~75-line Python algorithm that smooths an hourly demand forecast, then greedily places shifts one at a time to fill the largest coverage gap — respecting shift-length preferences, mandatory breaks, and concurrency limits. Then I ported that same algorithm 1:1 into the browser as a single self-contained HTML page that animates each shift landing across a coverage chart, a deficit heatmap, and a live roster. No framework, no bundler — compiled Tailwind is the only build step.
-
Step 1Smooth the forecast
Exponentially smooth the raw hourly staffing forecast (70% current hour / 30% carried) with a minimum-staffing floor, so coverage targets don't whipsaw hour to hour.
-
Step 2Greedily fill the deficit
Score every candidate shift by how much understaffing it absorbs per hour — weighted by shift-length and time-of-day preferences — place the best one, and repeat until coverage meets target.
-
Step 3Schedule around breaks
Shifts of 5h+ take an unpaid break ~60% of the way through (never during the noon or dinner peaks); break hours don't count toward coverage, so the optimizer plans around them.
Key decisions
Greedy heuristic vs. a constraint solver (OR-Tools). A solver is the right long-term answer once availability, cost, and labour law stack up — but it's a black box that demos badly and hides its reasoning. I chose greedy because every placement is explainable ("this shift absorbed the most understaffing per hour"), which is the entire point of a tool meant to show its work. I documented the solver as the honest next step rather than pretending greedy scales forever.
Port to JS vs. keep Python behind an API. Keeping Python meant a server, hosting, and network latency for what is fundamentally a client-side visualization. I ported the algorithm to JavaScript so the app is one static file that deploys anywhere — then verified the port produces schedules identical to the Python reference, so the logic stays trustworthy.
Outcome
- 2,000-line notebook → ~75-line core algorithm — the logic is now readable in one sitting.
- Browser port verified identical to the Python reference — same inputs, same schedule.
- 5 switchable demand scenarios (balanced weekday, lunch rush, dinner-heavy, late-night, weekend all-day), each recomputing and replaying instantly.
- Zero runtime dependencies — one self-contained HTML page. No CDN, no framework, no server.
What a production version needs
This is a prototype, deliberately scoped to the coverage problem — not a scheduling product. To be real it would need:
- Actual data — POS/sales-driven forecasts instead of hand-entered hourly arrays.
- Real employees — availability, max hours, overtime, seniority, skills, and time-off. Right now every "employee" is interchangeable.
- Cost optimization — labour cost % against sales and overtime avoidance, not just coverage.
- Employment-standards compliance — meal/rest breaks, rest between shifts, weekly hour caps, and reporting pay. A phased plan for Saskatchewan (The Saskatchewan Employment Act) is written up in the repo.
- A real solver — greedy demos well but makes defensibly-wrong trade-offs once availability, cost, and legal constraints stack up. Constraint programming (OR-Tools) is the right tool at that point.