A monthly shift scheduler for medical residents, with an assistant that only proposes
A residency coordinator built each month's shift schedule by hand in a spreadsheet. I built a scheduler that solves the month in the browser, and an assistant that turns her messages into changes she approves before anything moves.
Updated15 Oct 2026
The problem
A hospital residency programme needed a fair monthly shift schedule. There are many rules: which residents may cover which shifts, the day after a night, academic half-days, a weekly hours cap with compensatory half-days when a week runs over, free weekends and personal requests. Residents and doctors needed to see the published schedule on their phones without logging in, and only the coordinator should be able to change it.
What was out there
The schedule lived in a spreadsheet. The project notes record no off-the-shelf scheduler being considered.
What I built
It has two parts: a public page where residents find their name, see their shifts and subscribe to them in their calendar, and a private back office where the coordinator generates the month, edits it, checks it and publishes it.
The month is a mixed-integer program that HiGHS solves in the browser. Hard rules are constraints, fairness and spacing are penalties, and every slot has a costly "empty" option, so the model always returns an answer and shows which slots nobody can legally cover. After publishing, a repair mode handles an absence, a replacement or a swap with the fewest changed cells, and shows every moved cell before it lands in the draft.
An assistant reads the coordinator's messages in Spanish and turns them into a checklist of proposals. Nothing changes until she ticks them. The scheduling itself stays with the solver and the checker.
Equipment
- Claude Code Harnesses and agents
- Claude (Opus 4.6 to 5.5, Sonnet 5.5) AI models
- Codex CLI Harnesses and agents
- GPT (GPT-5.5 to GPT-6.1 in Codex) AI models
- HiGHS Code and infrastructure
- Vercel Code and infrastructure
- Turso Code and infrastructure
- Cloudflare Workers Code and infrastructure
- GitHub and GitHub Pages Code and infrastructure
How I built it
- I wrote the scheduling engine and the checker on fictional residents, with Colombian public holidays built in.
- I turned the coordinator's rules and answers into rule cards and solver terms, one round at a time, with Codex reviewing each change.
- I moved hosting from Cloudflare Workers to Vercel with a Turso (SQLite) database, so the app could sit on a subdomain with a single DNS record.
- I built the assistant with Claude tool use, scored it against an answer key I made by hand and compared six model settings before choosing.
- I added minimal-change repair (written by Codex), then gave the assistant the same repair engine. The server checks every move it proposes again before applying it.
Results
- The solver builds a month in about 4 seconds in the browser. The first real month came out with 189 shifts, 21 half-days and 0 rule errors.
- The hosting move kept all 762 database rows identical, and all 8 read endpoints returned byte-identical responses on the old and new hosts.
- The assistant prototype got 28 of 30 facts right on a real month's message. Both misses were genuine ambiguities, and it raised them itself.
- Model comparison on the same test, three runs per setup:
| Setup | Facts (of 30) | Ambiguities raised (of 6) | First reply |
|---|---|---|---|
| Opus 5.5, high | 27, 27, 27 | 6.0 | 112 s |
| Opus 5.5, medium | 28, 29, 29 | 5.7 | 103 s |
| Sonnet 5.5, high | 27, 28, 29 | 4.3 | 69 s |
| Sonnet 5.5, medium | 27, 28, 29 | 5.3 | 57 s |
| Haiku 5.5, high | 26, 26, 26 | 5.3 | 89 s |
| Haiku 5.5, medium | 26, 27, 27 | 5.3 | 65 s |
- I chose Opus 5.5 at medium effort for preparing a month and Sonnet 5.5 at medium for quick changes during the month.
- In a fictional test month on a copy of the database, the assistant's first reply took 54 s and held 16 proposals and 5 questions. The solver then built 196 shifts in 8 s with 0 errors and 0 empty slots, and five later change requests were all applied with 0 errors.
- The repair engine has 142 regression checks, and the assistant's moves have another 22.
What I learned
Make the assistant re-read the live state on every turn. Once it repeated its own wrong claim from an earlier chat, that a rule was switched off, when the rule was on. Now it must call its state tool in the same turn before it answers.
On my test, medium effort did as well as high, and the smallest model was not good enough. Haiku extracted facts well, but in all three high-effort runs it blocked one resident's shifts on every day instead of only on Mondays, even after the correction.
Lab notes
-
I started storing every assistant message and every request it could not apply, so I could read where it went wrong. Then I let it work on generated schedules: it can read the month, even out nights and propose swaps, still only as proposals.
-
An assistant prototype got 28 of 30 facts right on a real month's message, and I compared six model settings on the same test before giving it a tab in the back office. I also added a repair mode that fixes a published month with as few moves as possible.
-
I coded the coordinator's written rules, split the solver into two stages and started real use, with a roster loader that keeps personal data out of the code repository. I also moved the app from Cloudflare Workers and D1 to Vercel and Turso, and all 762 database rows came across identical.
-
I built the first engine: Colombian public holidays, a rule checker and a mixed-integer solver (HiGHS) that runs in the browser, all tested on fictional residents.