callroster
Builds the on-call schedule for a physician group — who works which night, fairly, under rules the group actually has.
Deployed and running. One install handles as many call lists as a hospital has services, each with its own people, rules and year.
The gap it fills
Shared calendars display a call roster. None of them build one. That build is a person with a spreadsheet, once a month, trying to hold a year of fairness and a dozen rules in their head at the same time — and every swap afterwards means redoing it.
This collects each provider's preferences and time off, solves the whole period at once, and then shows its work, because a roster nobody can interrogate is a roster somebody will simply overrule.
Local search, deliberately
The promise the product makes is that a group can describe a new rule and have it working the same day. Under a weighted scoring model a new soft rule is one term added to the score. Under a constraint program it is a re-modelling exercise, and the group waits. At this size — eight to twelve providers over a year — local search reaches a very good roster in seconds, and that trade is worth making.
Hard constraints are not weights. They are enforced by construction and by which moves are legal at all, so no roster ever violates one, including halfway through the search.
Four things that were not obvious
Construction has to work in whole blocks. Assigning day by day to whoever is least loaded produces a mess of one-day fragments that local search cannot reassemble, because every intermediate step scores worse and the search never crosses the valley between them. Starting with the right shape took the score from 23,500 to 1,400.
Multi-day moves have to be placed incrementally. Checking each day of a block on its own lets a five-day run land on someone capped at three consecutive days, because the block's other days are not on the board yet when each one is checked. There is a test named for exactly that.
A re-solve has to start from the existing roster rather than be scored towards it. Building from nothing and letting a stability term pull the result back does not work — the search cannot claw back hundreds of days in a few seconds.
And the search temperature has to be a bounded number rather than a fraction of the current score, because an anchored re-solve opens with an enormous fairness term that would otherwise set the temperature absurdly high and shake the roster apart before it settles.