← All experiments

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.

  • Tools
  • Automation
  • Health

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.

Back office of the shift scheduler showing November 2026 by resident: one row per resident with weekly hour totals, and coloured shift codes such as NG, NP, AP, UA and DG on each day of the month.
A whole month generated in the browser. Each resident's weekly hours sit beside their row, with a note on how long the solver took.
Public schedule page for October 2026: three cards for today, tomorrow and the day after with who covers each day and night shift, above a table of days and shift columns filled with residents' short names.
What residents see without logging in: the next three days at a glance, then the full published month.

Equipment

How I built it

  1. I wrote the scheduling engine and the checker on fictional residents, with Colombian public holidays built in.
  2. I turned the coordinator's rules and answers into rule cards and solver terms, one round at a time, with Codex reviewing each change.
  3. 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.
  4. 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.
  5. 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

  1. 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.

  2. 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.

  3. 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.

  4. 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.

© 2026 Johnattan García-Ruiz Last updated