Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
  • Platform
Try models
  • Employee Shift Scheduling
  • Employee resource constraints
  • Fairness

Employee Shift Scheduling

    • Introduction
    • Planning AI concepts
    • Metrics and optimization goals
    • Getting started with employee shift scheduling
    • Understanding the API
    • Employee shift scheduling user guide
    • Employee resource constraints
      • Employee availability
      • Employee contracts
      • Employee contracts: period rules
      • Employee contracts: shift rules
      • Fairness
      • Pairing employees
      • Shift travel and locations
    • Shift service constraints
      • Alternative shifts
      • Demand and supply
      • Mandatory and optional visits
      • Shift assignments
      • Shift sequence patterns: single day shifts
      • Shift sequence patterns: multi-day shifts
      • Shift sequence patterns: daily shift pairings
      • Skills and risk factors
    • Recommendations
    • Real-time planning
    • Time zones and Daylight Saving Time (DST)
    • New and noteworthy
    • Upgrading to the latest versions
    • Feature requests

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:

  • Shift assignments without fairness

  • Shift assignments with fairness rules

  • Factoring work history into fairness

  • Filtering fairness with tags

Prerequisites

To run the examples in this guide, you need to authenticate with a valid API key for the Employee Shift Scheduling model:

  1. Log in to Timefold Platform: app.timefold.ai

  2. From the Dashboard, click your tenant, and from the drop-down menu select Tenant Settings, then choose API Keys.

  3. Create a new API key or use an existing one. Ensure the list of models for the API key contains the Employee Shift Scheduling 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.

fairness no rules

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.

fairness with rules

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%.

fairness including work history
  • 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: period rules.

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.

fairness filtering employees with tags
  • 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

  • See the Employee shift scheduling user guide

  • 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.

  • © 2025 Timefold BV
  • Timefold.ai
  • Documentation
  • Changelog
  • Send feedback
  • Privacy
  • Legal
    • Light mode
    • Dark mode
    • System default