On-Call Rotation Schedule Maker
On-call is not quite a shift, and the difference matters before you enter any data. A person on shift is working; a person on call is available, usually at home, usually not paid the same, and usually able to be on call more often than they could work. This tool models shifts. It handles on-call well when you treat each on-call window as a shift with a demand of one and adjust the cost and hour limits to reflect that on-call is cheaper and less onerous than being at a desk — but if you leave your normal hourly rates and contracted hours in place, the model will price a week of on-call like a week of work and give you a strange roster.
Set up that way, the fit is good. A primary and a secondary on call around the clock is two
demand streams, the secondary flagged concurrent if the same rotation covers both
tiers or left separate if they are genuinely different people. Follow-the-sun becomes three
eight-hour windows — which is exactly the sample loaded on this page — and the
handoff points fall out of the shift times you enter. Escalation qualification is a skill: only
engineers who can actually take a page for a given system get a skill row for it.
The thing this does better than a spreadsheet rotation is fairness under constraints. A naive
round-robin looks fair and is not, because holidays, conferences and someone joining in month
two shift the pattern and it never recovers. Mark real unavailability, turn on
EquityPenalty and WeekendFairnessPenalty, and the model spreads both
total load and weekend load across the team while still respecting who is qualified for what.
The per-person counts on the results page are then the answer to “am I on call more than
everyone else,” which is a question worth being able to answer with a number.
Two things to be clear about. There is no paging integration — this produces a roster, not a PagerDuty or Opsgenie schedule, though the Excel export is a reasonable source to import from. And it does not model the compensatory rest that follows a bad night: if someone is paged at 03:00, the rule that they should not be at their desk at 09:00 is a policy decision you make after the fact, not something a roster built in advance can know.
shifts,
demand all, supply all, skills, one grid per demand and
per person, parameters. Download a sample first if you want the template.On-call rotation questions
How should I set costs for on-call? Lower than your working rate, and proportional to what on-call actually costs you — a flat standby allowance divided by the window length is a reasonable approximation. Leaving full salary rates in place makes the solver treat on-call as expensive labour and minimise it, which is not what you want.
Primary and secondary at the same time? Two demands. Use concurrent on
the secondary if the same person can hold both tiers of a rotation; keep them independent if
your secondary must be a different engineer, which is the usual arrangement.
Can it do follow-the-sun across time zones? Yes, as long as you pick one reference time zone and enter every window in it. Three eight-hour windows is the standard three-region pattern; give each engineer availability only for the windows their region covers.
Will it stop the same person getting every weekend? That is what
WeekendFairnessPenalty is for, with EquityPenalty to flatten total
load. Both are soft, so cover still wins where it has to — and the results page shows the
per-person weekend counts so the outcome is visible rather than assumed.
Does it integrate with PagerDuty or Opsgenie? No. It produces a roster and an Excel export; loading that into your paging tool is a manual step.
The same solver, other settings
Each of these loads a different worked example and is written for that setting, but they all run the same cross-day engine — if none of them matches yours, start from whichever is closest and replace the data.
- contact centre schedules — and how to tell whether you want this page or the other one
- hotel front desk and night audit — three shifts, a night auditor, and a very small team
- the general 24/7 shift schedule maker — any round-the-clock operation; start here if none of the others fit
How this differs from the other two modes
The staff scheduling optimizer on the home page works in half-hour slots inside a single day: you say how many people you need at 08:00, at 08:30, at 09:00, and it invents shift blocks to match. That is the right model when demand rises and falls through the day — a call centre, a restaurant, a shop — and the wrong model here, because every one of its rules is scoped to one day column. A 19:00–07:00 night becomes two disconnected half-shifts, and no rest rule can span the midnight boundary.
This page replaces the time axis with a shift axis. Your shifts are named blocks with real clock times, and an end time earlier than the start time simply means the shift crosses midnight. Rest between two shifts then becomes arithmetic on absolute minutes across the whole horizon rather than a special case, which is what makes cross-day rules — minimum rest, maximum consecutive days, maximum consecutive nights, no day shift after a night — expressible at all.
The third mode, the shift rotation generator, does not optimize anything: it lays out a named pattern — DuPont, Panama, Pitman, 4-on-4-off — across four crews. Use it when you want a known, published rotation and you do not need the schedule to respond to who is qualified for what, who is on holiday, or which shifts need three people instead of two.
What the solver actually decides
Coverage, skills, availability, pinned assignments and contracted hours are hard — the model physically cannot assign someone to work they are not qualified for or not available for. Coverage shortfall, minimum-hours shortfall, weekend fairness and cross-person equity are soft: each carries a penalty rather than a prohibition. That is a deliberate choice. A roster tool that answers “infeasible” tells you nothing useful; this one always returns a roster and then names the compromises it had to make, in the warnings and the unfilled-posts table.
Why the continuity numbers are on the results page
A roster can be 100% covered and still be unliveable. Everyone in a pay grade earns the same rate and coverage pins the total hours, so rearranging who works when often costs the model literally nothing — the same ward solved twice with staff listed in a different order produced 21 different shifts at an identical objective. Left alone, the solver returns whichever scattered roster it reached first. So the model carries a small penalty on each new block of consecutive days worked, and another on flipping between days and nights, and the results page reports what came out: how many blocks, how long they are, how many are lone single days, and the tightest turnaround anyone actually got. Those are the numbers to argue with, not the objective value.
Common questions
Is it free? Yes — no account, no trial. Your workbook is read to solve the model and not stored.
How many people and days can it handle? Up to 40 people and 31 days per solve. The 28-day hospital ward sample builds roughly 4,300 variables and solves in well under a minute.
What if I want a shift to be someone specific? Put f in that person's grid cell.
Forced cells are hard — the solver schedules around them.
Is minimum rest really 11 hours? Only by default. 11 hours is the EU Working Time Directive
figure; the US has no federal equivalent and state rules vary, so it is a parameter
(MinRestHours) rather than a constant. Set it to whatever your jurisdiction or contract
says.