> ## Documentation Index
> Fetch the complete documentation index at: https://www.planmyrounds.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# How the PlanMyRounds scheduling engine works

> A full reference for every input, constraint, fairness rule, and trade-off the PlanMyRounds scheduling engine reasons about when generating a duty roster.

Rostering looks simple until it isn't. Real rosters have to balance staffing minimums, seniority, leave, fairness, fatigue, and a long tail of doctor preferences — often across dozens of people and hundreds of shifts.

This page is a complete, no-shortcuts reference of every input, rule, and weighted trade-off the PlanMyRounds engine considers when it builds a roster. It exists so you know exactly what you're trusting the engine with.

> [!NOTE]
>
> This is a reference page. If you're new to PlanMyRounds, start with [How rosters work](/docs/rosters/overview) and [Creating a roster](/docs/rosters/creating-a-roster) first.

## At a glance

> **Two-phase solver**
>
> A fast greedy pass seeds the schedule, then an OR-Tools constraint-programming solver refines it against a weighted objective.

> **Hard vs. soft constraints**
>
> Hard constraints are never violated. Soft constraints are optimised — the engine bends them only when there's no valid alternative.

> **Fresh data every run**
>
> Doctor profiles, approved leave, and shift definitions are re-read on every generation so the output reflects the latest state.

> **Full violation report**
>
> Every run produces a structured report — hard violations block publishing; soft violations surface as warnings.

***

## Roster inputs

The top-level fields required to start a roster run.

| Parameter | Required | Description |
| - | - | - |
| **Roster Name** | Yes | Display name for the duty plan. |
| **Department** | Optional | Department scope for this roster. |
| **Start Date** | Yes | First day of the duty period (inclusive). |
| **End Date** | Yes | Last day of the duty period (inclusive). |
| **Selected Doctors** | Yes | The pool of doctors to schedule from. |
| **Shift Configuration** | Yes | The shift types that need to be filled each day. |
| **Scheduling Rules** | Yes | Constraint and fairness settings. |
| **Engine Type** | Optional | The variant of the engine to use. Defaults to the general engine; specialised variants are available for ICU rotations. |

***

## Doctor parameters

Every doctor in the roster pool carries the following fields. The engine reads them on every run — keeping doctor profiles up to date is the single biggest lever for better rosters.

### Identity and eligibility

| Parameter | Required | Description |
| - | - | - |
| **Seniority Level** | Yes | `senior` or `junior`. Used to enforce minimum senior counts per shift. |
| **Status** | Yes | Only `active` doctors are eligible for scheduling. |
| **Joining Date** | Yes | Used to calculate the new-doctor grace period (see [Soft constraints](#soft-constraints-and-fairness-optimisation)). |
| **Department** | Yes | Department affiliation. |
| **Pod / Group** | Optional | Sub-group used for cohort-based scheduling and reporting. |
| **Role** | Optional | `Non-Trainee` or `Trainee`. |

### Leave and availability constraints

The engine pulls a fresh snapshot of [leave data](/docs/leave/overview) on every run.

| Parameter | Type | Description |
| - | - | - |
| **Approved Leaves** | Hard constraint | Dates and shifts where the doctor is fully blocked. Only `approved` status leaves are applied. |
| **Excluded Shifts** | Soft constraint | Specific date + shift combinations the doctor wants to avoid. Penalised, not blocked. |
| **Excluded Shift Types** | Soft constraint | Shift names (for example, *Night*) the doctor prefers not to work across the entire period. |
| **Excluded Weekdays** | Soft constraint | Day-of-week + shift combinations (for example, *no night shifts on Mondays*). Expanded into date-level exclusions during preprocessing. |
| **Min Hours Per Month** | Soft constraint | Minimum working hours the doctor should receive in the period. |
| **Max Hours Per Month** | Soft constraint | Maximum working hours the doctor should receive in the period. |

> [!NOTE]
>
> Only leaves with **Approved** status create hard blocks. Pending or rejected leaves are ignored by the engine — always [clear the pending queue](/docs/leave/approval-workflow) before generating.

***

## Shift configuration

Every shift type used in the roster is defined by these fields.

| Parameter | Required | Description |
| - | - | - |
| **Shift Name** | Yes | Display label (for example, *Day*, *Night*, *Evening*). |
| **Start Time** | Yes | 24-hour format (HH:MM). |
| **End Time** | Yes | 24-hour format. Can cross midnight. |
| **Duration** | Auto-calculated | Derived from start and end time, in decimal hours. |
| **Required Doctors** | Yes | Total headcount needed to fill this shift each day. |
| **Min Seniors Required** | Yes | Minimum number of senior doctors within the required headcount. |
| **Color** | Optional | UI display colour for the calendar view. |

### Weekly shift configuration

When using the weekly engine, shifts can be configured per day of week:

* Monday through Sunday can each carry a different shift list.
* A separate **Holiday Shifts** set overrides the day-of-week config on public holidays.
* The engine pre-computes metadata: total weekdays, total weekends, coverage percentage, overlaps, and gaps.

***

## Scheduling rules (hard constraints)

Hard constraints are non-negotiable. The engine will not produce an assignment that violates them — if it can't, generation fails.

| Rule | Default | Description |
| - | - | - |
| **Max Consecutive Work Days** | 6 | A doctor cannot be assigned more than this many days in a row. |
| **Min Rest Days Per Week** | 1 | Each doctor must have at least this many rest days per Monday–Sunday week. |
| **Min Rest Hours Between Shifts** | 8 | Minimum gap required between the end of one shift and the start of the next. |
| **Allow Back-to-Back Duties** | `true` | Whether consecutive-day assignments are permitted at all. |
| **Back-to-Back Shift Pair Rules** | None | Specific pairs that are blocked regardless of the global setting (for example, *Night → Day* on consecutive days). |
| **No Shift Overlap** | Always on | A doctor cannot be assigned two shifts on the same day. |
| **Minimum Staffing** | Always on | Every shift must meet `required_doctors`. |
| **Minimum Senior Staffing** | Always on | Every shift must meet `min_seniors_required`. |
| **Recovery After 1 Shift** | Per shift | Minimum rest required after a single occurrence of this shift before the doctor can be assigned again. |
| **Recovery After 2 Consecutive Shifts** | Per shift | Applies when Back-to-Back Duties is enabled — a longer rest is required after two shifts in a row. |

***

## Soft constraints and fairness optimisation

Soft constraints are optimised, not guaranteed. The engine assigns scores and penalties to guide the solver toward the best achievable outcome.

### Fairness weights

The engine uses a weighted objective function. Higher weight means higher priority for the solver.

| Objective | Weight | Description |
| - | - | - |
| **Total Workload Balance** | 10 | Distributes total shift count as evenly as possible across all doctors. |
| **Shift Type Balance** | 3 | Ensures each doctor gets a balanced mix of day and night shifts. |
| **Preference Adherence** | 1 | Minimises violations of excluded shifts, shift types, and weekday rules. |

### Hours balancing

| Parameter | Default | Description |
| - | - | - |
| **Min Hours Per Doctor** | 120 hrs | Floor for the period — doctors below this trigger a soft violation. |
| **Max Hours Per Doctor** | 300 hrs | Ceiling for the period. |
| **Target Hours Per Doctor** | 150 hrs | Ideal hours — the engine optimises toward this value. |
| **Max Hours Difference** | 30 hrs | Maximum allowed variance between the highest- and lowest-loaded doctors. |

### Other soft rules

| Rule | Description |
| - | - |
| **Prefer Same Shift Type Per Week** | When enabled, tries to assign a doctor to the same shift type across a full week. |
| **New Doctor Grace Period** | A number of days from `date_of_joining` during which a new doctor is excluded from complex shift types. |
| **Proportional Fairness** | Doctors with more approved leave receive proportionally fewer shifts — the engine adjusts targets accordingly. |
| **Fairness Outlier Detection** | Uses a Z-score threshold (> 2.0) to flag doctors who are unusually over- or under-assigned relative to the group. |

***

## Violation reporting

After every run, the engine produces a structured violation report. Each violation contains:

| Field | Description |
| - | - |
| **Rule ID** | Machine identifier (for example, `insufficient_doctors`, `minimum_hours_not_met`). |
| **Type** | `hard` — must be resolved before publishing. `soft` — advisory only. |
| **Doctor** | The affected doctor's name and ID. |
| **Shift** | The affected shift name. |
| **Date** | The affected date. |
| **Details** | Plain-language description of what was violated and why. |

> [!NOTE]
>
> **Hard violations block publishing.** Soft violations surface as warnings and do not prevent publishing — you can review and accept them.

***

## Constraint priority order

When the engine encounters conflicts, it resolves them in this exact order.

### Hard (non-negotiable)

**1. No overlapping shifts**

A doctor cannot be assigned two shifts on the same day.

**2. Approved leave blocks respected**

The doctor is fully blocked on approved leave dates.

**3. Minimum shift staffing met**

Every shift instance reaches `required_doctors`.

**4. Minimum senior staffing met**

Every shift instance reaches `min_seniors_required`.

**5. Max consecutive work days respected**

No doctor exceeds the configured cap.

**6. Min rest days per week respected**

Each doctor gets the configured rest days per Monday–Sunday week.

**7. Back-to-back duty rules respected**

Global setting and shift-pair exceptions are both honoured.

**8. Recovery windows respected**

A doctor isn't reassigned before their configured recovery time after 1 or 2+ consecutive shifts has passed.

### Soft (optimised)

1. **Total workload fairness** (weight 10)
2. **Shift type balance** (weight 3)
3. **Excluded shift / shift type / weekday preferences** (weight 1)
4. **New doctor grace period**
5. **Min / max hours per month**
6. **Proportional and outlier-based fairness**

***

## Solver behaviour

The engine runs in two phases.

**1. Greedy phase**

Assigns senior doctors first, then fills remaining slots using fairness-based ranking. This produces a valid starting solution quickly.

**2. OR-Tools optimisation phase**

A constraint-programming solver (Google's OR-Tools) refines the greedy result against the weighted objective function until the time budget is reached or the solver proves optimality.

### Solver timeouts

Timeouts scale with the size of the problem.

| Problem size | Timeout |
| - | - |
| **Small** | 180 seconds |
| **Medium** (> 100 variables) | 180 seconds |
| **Large** (> 200 variables) | 275 seconds |

***

## Related reading

> **[How rosters work](/docs/rosters/overview)**
>
> The roster lifecycle as your team sees it.

> **[Roster constraints](/docs/rosters/roster-constraints)**
>
> A friendlier walkthrough of the work, rest, and fairness rules.

> **[Creating a roster](/docs/rosters/creating-a-roster)**
>
> The four-step wizard that feeds these parameters in.

> **[Leave management](/docs/leave/overview)**
>
> How approved leave is treated as a hard constraint.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.