Employee scheduling¶
Employee shift scheduling assigns employees to shifts: every shift gets an employee with the skill it
needs, nobody works overlapping shifts, on unavailable days, or without ten hours' rest, and the rest is
balanced against preferences. It is a STABLE model.
| Registration key | employee-scheduling |
| Version | v1 |
| Base path | /api/models/employee-scheduling/v1 |
| Entity | schedules, so a dataset is /v1/schedules/{id} |
| Score | HardSoftBigDecimalScore, written 0hard/-3603soft; hard and soft levels only |
| Features | pinning, real-time planning, weights |
Input¶
The input is an EmployeeScheduleInput: a list of employees and a list of shifts. Times are ISO-8601.
{
"employees": [
{ "name": "Amy Cole", "skills": ["Doctor"],
"unavailableDates": ["2026-10-07"], "undesiredDates": ["2026-10-11"], "desiredDates": ["2026-10-09"] }
],
"shifts": [
{ "id": "2026-10-05-Ambulatory-0", "start": "2026-10-05T06:00:00", "end": "2026-10-05T14:00:00",
"location": "Ambulatory care", "requiredSkill": "Doctor", "employee": null, "pinned": false }
]
}
| Field | Required | Meaning |
|---|---|---|
employees[].name |
yes | Unique; what a shift's employee refers to |
employees[].skills |
yes | The skills a shift may require |
employees[].unavailableDates, undesiredDates, desiredDates |
no | Dates the employee cannot, would rather not, or would like to work |
shifts[].id |
yes | Unique |
shifts[].start, end |
yes | The shift's window |
shifts[].location |
yes | Reported in the input metrics |
shifts[].requiredSkill |
yes | An employee without it is a hard constraint violation |
shifts[].employee |
no | An existing assignment; kept as the starting point |
shifts[].pinned |
no | true keeps the assignment fixed while solving |
Output¶
The output is an EmployeeSchedule: the input with every shift's employee filled in. It is accepted
unchanged as an input, which is how a solved schedule is continued or patched.
Constraints¶
Each constraint has a weight field in config.model.overrides, an integer of zero or more; the default
is 1 and 0 disables the constraint.
| Constraint | Weight field | Level | Penalised |
|---|---|---|---|
| Missing required skill | missingRequiredSkillWeight |
hard | once per shift |
| Overlapping shift | overlappingShiftWeight |
hard | per overlapping minute |
| At least 10 hours between 2 shifts | atLeast10HoursBetweenTwoShiftsWeight |
hard | per missing minute |
| Max one shift per day | maxOneShiftPerDayWeight |
hard | per extra shift |
| Unavailable employee | unavailableEmployeeWeight |
hard | per minute |
| Undesired day for employee | undesiredDayForEmployeeWeight |
soft | per minute |
| Desired day for employee | desiredDayForEmployeeWeight |
soft | rewarded per minute |
| Balance employee shift assignments | balanceEmployeeShiftAssignmentsWeight |
soft | by unfairness |
A score analysis names constraints by the first column.
KPIs and input metrics¶
| KPI | Type | Meaning |
|---|---|---|
assignedShifts |
integer | Shifts with an employee |
unassignedShifts |
integer | Shifts without one; a feasible schedule has none |
workingTimeFairnessPercentage |
number | How evenly working time is spread, as a percentage |
disruptionPercentage |
number | How many assignments changed from the solution this one continued from |
| Input metric | Meaning |
|---|---|
employees, shifts, pinnedShifts, locations |
Counts from the input |
Validation issues¶
Model-level validation runs on a worker after the schema has passed. An error stops the dataset at
DATASET_INVALID; a warning is reported and solving continues.
| Code | Severity |
|---|---|
EmptySchedule |
error |
NoEmployees |
error |
DuplicateEmployeeName |
error |
DuplicateShiftId |
error |
ShiftUnknownEmployee |
error |
ShiftEndsBeforeStart |
error |
RequiredSkillUnknown |
warning |
Patch paths¶
A from-patch request may address /employees by name and /shifts by id:
/employees/[name=Amy Cole]/unavailableDates, /shifts/[id=2026-10-05-Ambulatory-0]/pinned, and
/shifts/- to append.
Demo datasets¶
| Id | Description | Recommended spentLimit |
|---|---|---|
SMALL |
15 employees, 2 weeks, 3 locations | PT30S |
LARGE |
50 employees, 4 weeks, 7 locations | PT5M |
Both are generated from a fixed seed and start on 2026-10-05, so a solve of the same demo is comparable
across runs. GET /v1/demo-data/SMALL returns a whole request with the recommended configuration, and
GET /v1/demo-data/SMALL/input the input alone.