Fairness
Balancing the workload fairly across employees is an important factor in employee shift scheduling.
Employees like to know they are getting a fair share of the available shifts, and they are not assigned an unfair number of undesirable shifts, for example, the late shift.
Ensuring fairness by spreading the workload evenly can help with employee satisfaction and retention.
Fairness can be determined in a number of ways. For employee shift scheduling you can balance the number of shifts assigned and the time worked.
Fairness is defined by adding global rules and can be filtered to apply to employees and shifts with specific tags.
This guide explains how to manage fairness with the following examples:
Prerequisites
To run the examples in this guide, you need to authenticate with a valid API key for this model:
-
Log in to Timefold Platform: app.timefold.ai
-
From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.
-
Create a new API key or use an existing one. Ensure the list of models for the API key contains the current model.
In the examples, replace <API_KEY>
with the API Key you just copied.
1. Shift assignments without fairness
In the following example, there are 6 shifts and 3 employees. However, there are no fairness rules.
Beth and Ann are assigned 3 shifts each, and Carl is not assigned any shifts. This is not a fair distribution of the shifts.
This schedule was generated from the following input dataset:
-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Fairness example - no fairness rules"
}
},
"modelInput": {
"employees": [
{
"id": "Ann"
},
{
"id": "Beth"
},
{
"id": "Carl"
}
],
"shifts": [
{
"id": "2027-02-01-a",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-01-b",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-02-a",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "2027-02-02-b",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "2027-02-03-a",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
},
{
"id": "2027-02-03-b",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Fairness example - no fairness rules",
"submitDateTime": "2025-02-11T06:50:39.592789134Z",
"startDateTime": "2025-02-11T06:50:45.413194152Z",
"activeDateTime": "2025-02-11T06:50:45.466680367Z",
"completeDateTime": "2025-02-11T06:55:45.669496464Z",
"shutdownDateTime": "2025-02-11T06:55:45.834078372Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "2027-02-01-a",
"employee": "Ann"
},
{
"id": "2027-02-01-b",
"employee": "Beth"
},
{
"id": "2027-02-02-a",
"employee": "Ann"
},
{
"id": "2027-02-02-b",
"employee": "Beth"
},
{
"id": "2027-02-03-a",
"employee": "Ann"
},
{
"id": "2027-02-03-b",
"employee": "Beth"
}
]
},
"kpis": {
"assignedShifts": 6,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": null,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null
}
}
modelOutput
contains the schedule with Ann’s and Beth’s assigned shifts.
2. Shift assignments with fairness rules
Fairness can be measured by time worked and shifts assigned. Both are defined as global rules:
{
"globalRules": {
"balanceTimeWorkedRules": [
{
"id": "balanceTime"
}
],
"balanceShiftCountRules": [
{
"id": "balanceShifts"
}
]
}
}
You can include either or both rules.
At a minimum, both rules must include an ID, and can optionally include or exclude tags.
Adding the rules will activate the soft constraints "Balance time worked" and "Balance shift count" which incentivizes Timefold to find a fair solution.
In the following example, we have the same 3 employees and 6 shifts, but this time the shifts are assigned fairly.

This schedule was generated from the following input dataset:
-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Fairness example - fairness rules"
}
},
"modelInput": {
"globalRules": {
"balanceTimeWorkedRules": [
{
"id": "balanceTime"
}
],
"balanceShiftCountRules": [
{
"id": "balanceShifts"
}
]
},
"employees": [
{
"id": "Ann"
},
{
"id": "Beth"
},
{
"id": "Carl"
}
],
"shifts": [
{
"id": "2027-02-01-a",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-01-b",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-02-a",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "2027-02-02-b",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "2027-02-03-a",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
},
{
"id": "2027-02-03-b",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Fairness example - fairness rules",
"submitDateTime": "2025-02-11T07:13:00.436645904Z",
"startDateTime": "2025-02-11T07:13:06.477400186Z",
"activeDateTime": "2025-02-11T07:13:06.539276754Z",
"completeDateTime": "2025-02-11T07:18:06.808626567Z",
"shutdownDateTime": "2025-02-11T07:18:07.011068988Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "2027-02-01-a",
"employee": "Ann"
},
{
"id": "2027-02-01-b",
"employee": "Beth"
},
{
"id": "2027-02-02-a",
"employee": "Carl"
},
{
"id": "2027-02-02-b",
"employee": "Ann"
},
{
"id": "2027-02-03-a",
"employee": "Beth"
},
{
"id": "2027-02-03-b",
"employee": "Carl"
}
]
},
"kpis": {
"assignedShifts": 6,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": 100.0,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null
}
}
modelOutput
contains the shift schedule with Ann, Beth, and Carl assigned a fair share of the shifts.
The KPIs in the output show this schedule has a workingTimeFairness
of 100%.
3. Factoring work history into fairness
By default, only the current planning window is considered in determining the fairness of the schedule. However, time worked and shifts worked in the past can also be considered to provide a fairer distribution over a longer period of time.
The timespan to determine fairness over could be the previous month or quarter, but by including specific time periods, for instance, the previous December, it can also be used so that the same people aren’t scheduled to work over the holidays each year.
To include previous minutes worked in the fairness calculation, add "employeeToPublishedMinutesWorked"
with the total number of minutes worked in previous periods for the employees.
For the fairness calculation to be accurate, it’s important to ensure the minutes worked for all employees are from the same period of time, and that the employees are generally available for the same number of minutes over the period. |
{
"globalRules": {
"balanceTimeWorkedRules": [
{
"id": "balanceTime",
"employeeToPublishedMinutesWorked": {
"Ann": 9600,
"Beth": 4800,
"Carl": 9600
}
}
]
}
}
To include previous shifts worked in the fairness calculation, add "employeeToPublishedShiftCount"
with the total number of shifts worked in previous periods for the employees.
For the fairness calculation to be accurate, it’s important to ensure the shifts worked for all employees are from the same period of time, and that the employees are generally available for the same number of shifts over the period. |
{
"globalRules": {
"balanceShiftCountRules": [
{
"id": "balanceShifts",
"employeeToPublishedShiftCount": {
"Ann": 10,
"Beth": 10,
"Carl": 5
}
}
]
}
}
In the following example, Ann and Carl have both worked 9600 minutes in the previous period, whereas Beth only worked 4800 minutes.
With employeeToPublishedMinutesWorked
included, Beth is assigned 3 shifts, Ann is assigned 2 shifts, and Carl is assigned 1 shift, making for a fairer assignment of shifts over a longer period of time than the current planning window.
The schedule has a workingTimeFairnessPercentage
of 71.13%.

-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Fairness example - including work history"
}
},
"modelInput": {
"employees": [
{
"id": "Ann"
},
{
"id": "Beth"
},
{
"id": "Carl"
}
],
"shifts": [
{
"id": "2027-02-01-a",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-01-b",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-02-a",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "2027-02-02-b",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "2027-02-03-a",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
},
{
"id": "2027-02-03-b",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
}
],
"globalRules": {
"balanceTimeWorkedRules": [
{
"id": "balanceTime",
"employeeToPublishedMinutesWorked": {
"Ann": 9600,
"Beth": 4800,
"Carl": 9600
}
}
]
}
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Fairness example - including work history",
"submitDateTime": "2025-02-18T04:40:46.50106078Z",
"startDateTime": "2025-02-18T04:40:52.356744742Z",
"activeDateTime": "2025-02-18T04:40:52.447854961Z",
"completeDateTime": "2025-02-18T04:45:52.686061129Z",
"shutdownDateTime": "2025-02-18T04:45:52.830273576Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/-334855soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "2027-02-01-a",
"employee": "Beth"
},
{
"id": "2027-02-01-b",
"employee": "Ann"
},
{
"id": "2027-02-02-a",
"employee": "Beth"
},
{
"id": "2027-02-02-b",
"employee": "Carl"
},
{
"id": "2027-02-03-a",
"employee": "Beth"
},
{
"id": "2027-02-03-b",
"employee": "Ann"
}
]
},
"kpis": {
"assignedShifts": 6,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": 71.13,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null
}
}
modelOutput
contains the schedule for Ann, Beth, and Carl.
4. Filtering fairness with tags
Tags can be used to filter how the fairness rules are applied to both shifts and employees.
For instance, if a casual employee works fewer hours than other employees, including them in the fairness calculation would negatively skew the results.
Similarly, shifts can be tagged and excluded or included in the fairness calculation.
The balanceTimeWorkedRules
and balanceShiftCountRules
can exclude (or include) employees.
In this example, "casual" employees are excluded from the rules.
{
"balanceTimeWorkedRules": [
{
"id": "balanceTime",
"excludeEmployeeTags": [
"casual"
]
}
],
"balanceShiftCountRules": [
{
"id": "balanceShifts",
"excludeEmployeeTags": [
"casual"
]
}
]
}
The same results would be achieved with "includeEmployeeTags": ["permanent"] .
|
Employees must be tagged to indicate which employees the rules apply to:
{
"id": "Dan",
"availableTimeSpans": [
{
"start": "2027-02-03T12:00:00Z",
"end": "2027-02-03T17:00:00Z"
}
],
"tags": ["casual"]
}
To filter shifts from the fairness calculation, add excludeShiftTags
or includeShiftTags
to the rule:
{
"globalRules": {
"balanceTimeWorkedRules": [
{
"id": "balanceTime",
"excludeShiftTags": [
"night"
]
}
],
"balanceShiftCountRules": [
{
"id": "balanceShifts",
"excludeShiftTags": [
"night"
]
}
]
}
}
Tag the shifts:
{
"shifts": [
{
"id": "2027-02-01-a",
"tags": [
"night"
]
}
]
}
In the following example, Ann, Beth, and Carl are permanent employees who all work two days a week. Dan is a casual employee who only works on Wednesday afternoon. The permanent employees have contracts that set their maximum minutes worked at 960 minutes per week.
Learn more about Employee contracts.
Even though Dan works fewer hours than everybody else, the fairness score is 100% because Dan as a casual employee is excluded from the calculation and everybody else is assigned a fair distribution of shifts.

-
Input
-
Output
Try this example in Timefold Platform by saving this JSON into a file called sample.json and make the following API call:
|
curl -X POST -H "Content-type: application/json" -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules [email protected]
{
"config": {
"run": {
"name": "Fairness example - filtering with employee tags"
}
},
"modelInput": {
"contracts": [
{
"id": "permanentContract",
"periodRules": [
{
"id": "Max24HoursPerWeekFullTime",
"period": "WEEK",
"minutesWorkedMax": 960
}
]
}
],
"globalRules": {
"balanceTimeWorkedRules": [
{
"id": "balanceTime",
"excludeEmployeeTags": [
"casual"
]
}
],
"balanceShiftCountRules": [
{
"id": "balanceShifts",
"excludeEmployeeTags": [
"casual"
]
}
]
},
"employees": [
{
"id": "Ann",
"contracts": [
"permanentContract"
],
"tags": [
"permanent"
]
},
{
"id": "Beth",
"contracts": [
"permanentContract"
],
"tags": [
"permanent"
]
},
{
"id": "Carl",
"contracts": [
"permanentContract"
],
"tags": [
"permanent"
]
},
{
"id": "Dan",
"availableTimeSpans": [
{
"start": "2027-02-03T12:00:00Z",
"end": "2027-02-03T17:00:00Z"
}
],
"tags": [
"casual"
]
}
],
"shifts": [
{
"id": "2027-02-01-a",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-01-b",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "2027-02-02-a",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "2027-02-02-b",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "2027-02-03-a",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
},
{
"id": "2027-02-03-b",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
},
{
"id": "2027-02-03-c",
"start": "2027-02-03T12:00:00Z",
"end": "2027-02-03T17:00:00Z"
}
]
}
}
To request the solution, locate the 'ID' from the response to the post operation and append it to the following API call: |
curl -X GET -H 'X-API-KEY: <API_KEY>' https://app.timefold.ai/api/models/employee-scheduling/v1/schedules/<ID>
{
"run": {
"id": "ID",
"name": "Fairness example - filtering with employee tags",
"submitDateTime": "2025-02-18T06:01:33.474783338Z",
"startDateTime": "2025-02-18T06:01:39.980125927Z",
"activeDateTime": "2025-02-18T06:01:40.071584237Z",
"completeDateTime": "2025-02-18T06:06:40.378992747Z",
"shutdownDateTime": "2025-02-18T06:06:40.588460546Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/0medium/0soft",
"tags": [
"system.profile:default"
],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "2027-02-01-a",
"employee": "Ann"
},
{
"id": "2027-02-01-b",
"employee": "Beth"
},
{
"id": "2027-02-02-a",
"employee": "Carl"
},
{
"id": "2027-02-02-b",
"employee": "Ann"
},
{
"id": "2027-02-03-a",
"employee": "Beth"
},
{
"id": "2027-02-03-b",
"employee": "Carl"
},
{
"id": "2027-02-03-c",
"employee": "Dan"
}
]
},
"kpis": {
"assignedShifts": 7,
"unassignedShifts": 0,
"workingTimeFairnessPercentage": 100.0,
"disruptionPercentage": 0.0,
"averageDurationOfEmployeesPreferencesMet": null,
"minimumDurationOfPreferencesMetAcrossEmployees": null,
"averageDurationOfEmployeesUnpreferencesViolated": null,
"maximumDurationOfUnpreferencesViolatedAcrossEmployees": null
}
}
modelOutput
contains the shift assignments for the week.
Next
-
Understand the constraints of the Employee Shift Scheduling model.
-
See the full API spec or try the online API.
-
Manage schedules with Time zones and Daylight Saving Time (DST) changes.
-
Manage Employee availability.