Correctional Officer Schedule Template and Maker
Corrections is the purest post-based scheduling problem there is. A hospital can flex a ward and a factory can idle a line, but a control room post, a housing unit and a perimeter tower are either staffed or the facility is out of compliance. That changes what the schedule is for: you are not distributing work across available people, you are proving that a fixed set of posts can be manned every hour of every day with the officers on your roster — and finding out, precisely, when they cannot.
Which is why the number this page is really about is the relief factor: how many officers you must employ per post to keep that post manned continuously once days off, vacation, training and sick leave are accounted for. Most facilities carry a figure from a staffing analysis and treat it as a constant. What it actually is, is the outcome of your rest rule, your maximum consecutive days and your leave burden interacting — and you can watch that here directly. Mark a realistic amount of leave as unavailability, solve, and read the unfilled-posts table. If posts go dark, your relief factor is too low for the rules you are enforcing, and the roster is telling you so before the overtime bill does.
That is the honest use of this tool in corrections. Mandatory overtime and holdovers are not a scheduling technique; they are what happens when the relief factor is short and the schedule has no slack to absorb an absence. The model will not hide that from you the way a spreadsheet will: it takes the shortage, names the post, names the day, and leaves the decision where it belongs.
The loaded sample is a small 21-day two-shift post with weekend fairness and workload equity switched on — the right shape to start from, and small enough to read end to end. Scale it by adding one demand per post you must staff, and give each officer skill rows only for the posts they are actually qualified and cleared to work. An officer with no skill row for a post cannot be assigned there under any cost, which is the behaviour you want for posts with training or classification requirements attached.
shifts,
demand all, supply all, skills, one grid per demand and
per person, parameters. The template above has all of them filled in.Correctional rostering questions
How do I model posts rather than headcount? One demand per post, each needing one officer per shift. That is more demand streams than most settings use and it is the right representation: it lets the model tell you which post went unstaffed, not just that you were two officers short.
Can it tell me my relief factor? Not as a single number, but it will show you whether the one you are using holds. Enter your real leave and training as unavailability, solve, and see whether any post goes unfilled. A roster that covers with slack to spare and one that collapses when two officers are off are different situations that the same headcount can describe.
Does it schedule mandatory overtime? No, deliberately. It reports the shortage instead
of quietly assigning someone a sixteenth hour. If you want to see what a holdover policy buys
you, raise max_hrs_day for the affected officers and re-solve — the
difference between the two rosters is the honest cost of the policy.
Some posts require an officer of a specific classification. Use skills. Only officers with a skill row for that post can be assigned to it, and no combination of costs will override it — unqualified assignment is structurally impossible rather than merely expensive.
How is this different from the security guard page? Same engine and a similar shape; the difference is scale and consequence. A security post that goes unfilled is a service failure. A correctional post that goes unfilled is a compliance failure, which is why this page is written around the relief factor and that one around fairness on a small team.
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.
- firefighter shift schedules — 24-hour shifts, platoons, and the rest rule that follows a 24
- police patrol schedules — watches, minimum beats, and every-other-weekend-off
- security guard posts — a small team covering one post, with weekend fairness switched on
- 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
Skills, availability, pinned (f) assignments, maximum hours and days, minimum rest and the
consecutive-day and consecutive-night caps are hard — the model physically cannot
assign someone to work they are not qualified for, not available for, or not rested for. Filling every
post, reaching each person's minimum hours, 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, so a post the rules cannot
fill comes back as an unfilled post, named in the warnings and the unfilled-posts table, not as a
failure. The one way to get “infeasible” is hard rules that contradict each other —
almost always an f cell that breaks a rest, consecutive-day or maximum-hours rule.
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 kept afterwards. We do cache some data on the server to speed up responses and keep an anonymous count of runs.
How many people and days can it handle? Up to 60 people, 42 days (six weeks), 12 shifts and 12 demand streams 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.