At IntelyCare, I was the architect and sole backend engineer on a nurse scheduling system — the software that decides who works which shift, at which facility, on which floor. I designed the data model, the rules engine, and the assignment logic behind it. It's one of those problems that looks simple from the outside. Every product demo shows a clean weekly calendar and a nurse tapping "accept." Then you put it in front of a real facility and discover that the calendar is the least interesting part. The interesting part is everything the calendar has to know before it's allowed to put a name in a box.
These are the design decisions I made for that reality — and most of it applies to any scheduling system where the rules come from regulators, contracts, and people rather than from the software.
Regulation is the first constraint, not an edge case
A nurse isn't interchangeable with any other nurse. Registered nurses, licensed practical nurses, and certified nursing assistants have different scopes of practice, and the rules a facility operates under specify how many of each type it needs — per floor, per unit, per building, per shift. Some states, like California, set minimum nurse-to-patient ratios for hospital units. Elsewhere, the requirements come from state nursing-home staffing rules, union contracts, accreditation, and the facility's own policies. And those numbers usually move with the patient census, so the staffing a floor needs on Tuesday night isn't necessarily what it needed on Monday.
That shaped the data model I designed from the first day. A shift isn't "8 a.m. to 4 p.m." It's "8 a.m. to 4 p.m., second floor, east building, requires an RN" — and the floor isn't covered until every required slot of every required type is filled. I made eligibility a hard constraint: the system never treats an unqualified assignment as valid, and no scheduler can click past that at 5 a.m. when they're short-staffed and in a hurry. But software can't manufacture available staff. When required coverage can't be met, the system keeps the gap visible and escalates it, rather than silently marking the floor as covered. In the data model I built, staffing requirements were keyed by unit, shift definition, and day of the week, with a required count for each staff type and subtype. Every requirement carried an effective date, so a facility could change its staffing going forward without rewriting history, and date-specific exceptions could override the normal requirement for a single day.
Shifts are standard — until they aren't
Every facility will tell you it runs standard shifts: days, evenings, nights. Then you look at the actual schedule. A nurse covers the first four hours of a night shift because someone called out late. Another stays two extra hours to cover a gap before the next shift arrives. A shift crosses midnight, or a daylight-saving change, and suddenly a "12-hour shift" is eleven or thirteen hours long. Partial shifts aren't exceptions you can handle later; they're a normal week.
The design decision I made was to stop thinking in shifts and start thinking in coverage over time. Instead of asking "is the night shift filled?", the system asks "for every hour on this floor, do we have the required number of each nurse type?" A partial shift then fills part of a gap and leaves the rest visible as still open, rather than either hiding it or pretending the whole shift is covered. In the system I built, standing schedules generated shifts weeks ahead from each nurse's recurring pattern, partial shifts were first-class records with their own start and end times, and exceptions for a specific date — including partial-hour requirements — were layered on top of the normal pattern instead of replacing it.
How many shifts can one nurse pick up?
The nurses who pick up the most shifts are often the most reliable people in the building — which is exactly why the system has to protect them from themselves. Facilities set limits on how many hours or shifts one nurse can work in a day or a week, how many consecutive hours they can be on the floor, and how much rest they need between shifts. A tired nurse at the end of a double is a patient-safety risk, not just a scheduling statistic. So every offer the system makes first checks whether accepting it would push that nurse past any of those limits — including hours they've already worked at a different facility, if the data is available.
Overtime, or offer it to someone else?
This was one of the most interesting problems, because it's where scheduling turns into money. Overtime rules depend on jurisdiction and on any applicable agreements. Under federal law, overtime generally applies after 40 hours in a workweek, though hospitals and residential care facilities can use an alternative "8 and 80" system, and some states, like California, add daily overtime on top. So when an open shift appears, the nurse who wants it most may also be the nurse for whom it costs time and a half — while another qualified nurse could work the same hours at straight time.
The same question applies to nurse type. If an LPN slot is open and an RN is available, the RN may be able to cover it, depending on credential scope, state rules, facility policy, and any applicable agreements — but the facility pays RN rates for LPN work, and that RN might be needed somewhere only an RN can go. A good scheduling system doesn't just find someone eligible. It finds the eligible option that costs the least and uses each nurse where they're most needed, then shows the scheduler why it ranked the options the way it did.
The first nurse who says yes isn't always the right answer. The right answer is the qualified nurse who can work the shift without overtime, without fatigue risk, and without leaving another floor short.
Seniority and fairness
Cost isn't the only thing that decides who gets offered a shift first. Many facilities give senior staff first choice of open shifts, or rotate offers so that the same people don't always get the best ones — and in a unionized workplace, that order is often written into the contract. Nurses notice immediately when it isn't followed. If the system ever seems to play favorites, people stop trusting it, and once they stop trusting it they go back to calling the scheduler directly. So the offer order had to be explicit, configurable per facility, and explainable: anyone should be able to see why a shift went to the person it went to.
Explainability came from history. In the system I designed, every offer, acceptance, decline, confirmation, call-out — with its reason — and status change was written as a history record with who did it and when, never overwritten. When someone asked why a shift went to a particular nurse, or why a floor was short last Tuesday, the answer was in the record rather than in someone's memory.
When to bring in outside nurses
Sometimes there's no one inside the building who can take a shift without breaking a rule. That's when a facility brings in outside nurses — per diem staff or an agency. Outside staffing is usually the most expensive option, so facilities don't want to use it too early; but it also needs lead time, so they can't wait until the last minute either. The system has to understand that escalation: offer internally first, watch how close the shift is getting, and open it to outside nurses at the point where waiting any longer risks leaving the floor below its required staffing. Getting that timing right is a business decision for each facility. The software's job is to make it explicit and automatic, instead of something a scheduler remembers to do at 4 a.m. In the version I built, an open shift could be offered to the facility's own staff and posted to IntelyCare's per diem network at the same time. If an internal nurse accepted first, the outside request was cancelled automatically; if an outside nurse accepted, they were added to the facility's roster as agency staff, with their credential type mapped to the facility's own staff types so the coverage rules still applied.
Hard rules vs. soft rules
The most useful design decision I made was separating rules into two kinds:
- Hard rules — license scope, credentials, and safety limits on hours and rest decide whether an assignment is valid at all. The system never accepts an assignment that breaks them, and no shortcut in the interface can override them. Required nurse types and counts define coverage — and when they can't be met, the gap stays visible and escalates.
- Soft rules — cost, overtime avoidance, seniority, rotation, and when to escalate to outside staff. The system optimizes for them, each facility sets their priority, and a scheduler can override them with a recorded reason.
Mixing the two is how scheduling systems end up either too rigid to use or too flexible to trust.
Under the hood: two nurses, one shift
First, the stack. The backend was Python and Django, with Django REST Framework serving the API that the nurse apps and scheduler dashboards called, and MySQL underneath. Scheduling data is deeply relational — facilities, units, shift definitions, staffing requirements, nurses, credentials, assignments, and their history all reference each other — so a relational database with real constraints and transactions was the natural fit. And as the only backend engineer, I wanted a framework whose ORM, migrations, and request handling were solved problems, so my time went into the scheduling rules rather than the plumbing.
Here's an illustrative example of the kind of backend problem a scheduling system has to get right. An open shift goes out to twenty nurses at once. Two of them tap "accept" within the same second. If the code reads the slot, sees it's open, and then writes the assignment, both requests can see "open" before either one writes — and the floor ends up with two nurses for one slot, or a nurse who was told "you're confirmed" and then gets a cancellation.
The fix is to make claiming the slot a single atomic operation, so the database itself decides who won. A conditional update only succeeds if the slot is still unassigned:
UPDATE shift_slots
SET nurse_id = :nurse_id, version = version + 1
WHERE id = :slot_id
AND nurse_id IS NULL
AND version = :expected_version;
-- 1 row updated: this nurse got the shift
-- 0 rows updated: someone else got there first
The second request updates zero rows and gets a clear "this shift was just filled" message instead of a false confirmation. But that only solves half the problem. The same nurse can also accept two overlapping shifts on two different floors at the same moment, and each one on its own passes the hour-limit check. So the eligibility checks — hours, rest, overlaps — have to run inside the same transaction as the claim, behind a stable serialization point for that nurse: a lock on the nurse's own record, or an advisory lock keyed to the nurse. Locking the nurse's existing assignment rows isn't enough, because it doesn't stop a concurrent transaction from inserting a new overlapping assignment. With every claim for the same nurse forced through one lock, the rules are evaluated against what's actually committed, not against a snapshot from a second ago. Notifications go out only after the transaction commits, so nobody is ever told about an assignment that didn't happen.
Adoption: designing for nurses who work on paper
The hardest part of the project wasn't the rules engine. It was getting the system used. Many of the nurses and schedulers had run scheduling on paper, whiteboards, and phone calls for years — and that system worked, in the sense that shifts got filled by someone who knew everyone's number and everyone's preferences. A new system had to be better than that person on day one, not eventually.
That meant designing for a nurse on a phone at the end of a twelve-hour shift, not for a manager at a desk: picking up a shift in a couple of taps, clear notifications, and no training manual. It meant letting schedulers see the same picture they used to keep on the whiteboard, so the new tool felt familiar instead of foreign. And it meant accepting that adoption happens floor by floor, with the schedulers who ran the old process involved in the rollout rather than having it handed to them. It also meant meeting paper halfway. The system could print a schedule as a PDF, sorted by time and then location, one unit per page, with blank rows left at the bottom so staff could still write in a last-minute change by hand. And nurses were reached where they already were: a text message when they were offered a shift, and another when it was approved.
What works for one facility doesn't work for the next
Every hospital and nursing home believed its way of scheduling was the normal way. Every one of them was different: different shift times, different staffing requirements, different overtime policies, different union rules, different ideas of when it's acceptable to call in outside help. Early on, it's tempting to handle each difference with a special case in the code. That works for the first few facilities and becomes unmaintainable by the tenth.
The decision I made early was to give each facility its own isolated database — a separate tenant, with every request routed to the right one — and to treat each facility's rules as configuration, not code. Shift definitions, staffing requirements, hour limits, offer order, and escalation timing all lived in data that could be set per facility — and sometimes per floor — without a deployment. The core engine stayed the same; what changed from one building to the next was the rule set it was given. I chose a database per facility on purpose. Facility data never needs to mix, and separate databases make a cross-facility leak much harder — a missing filter in a query can't return another facility's nurses — while letting each facility be backed up, restored, or moved on its own. The cost is operational: every new facility needs a database provisioned, every schema migration has to run across every tenant, and monitoring and connection management grow with the number of tenants. For data that must never mix, that trade was worth it.
If every new customer needs a code change, you haven't built a scheduling system. You've built a custom project you'll be maintaining forever.
The outcome
When I left IntelyCare, four facilities were running their scheduling on the system — and at those facilities it had replaced everything they used before: the paper schedules, the whiteboards, and the phone calls. The same engine served all four, each with its own rules, its own staffing requirements, and its own database.
Where this leaves you
Nurse scheduling taught me something that applies to almost any operational software: the hard part isn't the interface, and it isn't the algorithm. It's capturing the real rules — the regulations, the contracts, the cost trade-offs, the unwritten habits — and building a system that enforces the ones that can't bend, optimizes the ones that can, and lets each customer decide which is which. If you get that right, the calendar is easy. If you get it wrong, people go back to the whiteboard.
The calendar is the easy part. The real system is everything it has to know before it puts a name in a box.
Engineering leaders who want to see more of the systems I've designed and built can find my engineering portfolio at josephrounds.dev.