Employee contracts: Period rules
Employee contracts stipulate the conditions under which the employees work, such as their working hours, the number of hours worked per month, week, or day, and the amount of time that must occur between scheduled shifts.
Different groups of employees have different contracts, for instance, full-time employees may expect to work no more than 5 eight-hour shifts per week, while part-time employees might work 3 shifts per week.
Employees have an expectation that the terms of their contracts are honored in the shifts they are scheduled to work. For any employee shift scheduling solution to be feasible, it must take into account the contractual obligations between the employer and the employee.
This guide explains how to manage employee contract period rules 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. Defining a contract
Contracts include rules that must be applied to groups of employees. For instance:
-
Full-time employees work no more than 5 consecutive days.
-
Part-time employees work no more than 3 consecutive days.
-
Full-time and part-time employees work a maximum of 8 hours a day.
-
Full-time employees work a maximum of 40 hours per week.
-
Part-time employees work a maximum of 24 hours per week.
-
Full-time and part-time employees have at least 12 hours between shifts.
Contracts are defined in the modelInput.
{
"modelInput": {
"contracts": [
{
"id": "fullTimeContract"
},
{
"id": "partTimeContract"
}
]
}
}
Contracts
at a minimum need to include an id
that identifies the contract.
You can define as many contracts as required.
Individual employees reference the contracts that apply to them.
For instance, Ann is a full-time employee and includes fullTimeContract
.
{
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
]
}
2. Period rules
Period rules stipulate how many hours employees can work in a given period. For instance, a full-time employee might work a maximum of 8 hours per day and a maximum of 40 hours per week.
In the following example, Ann is a full-time employee who works 8 hours per day and 40 hours per week.
Period rules are defined in contracts
.
Multiple periodRules
can be defined.
{
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "Max8HoursPerDayFullTime",
"period": "DAY",
"minutesWorkedMax": 480,
"satisfiability": "REQUIRED",
"periodShiftOverlapKind": "START_AND_END"
},
{
"id": "Max40HoursPerWeekFullTime",
"period": "WEEK",
"minutesWorkedMax": 2400,
"satisfiability": "REQUIRED"
}
]
}
]
}
A periodRule
needs to include an id
for the rule, a period
(ie, DAY
, WEEK
, MONTH
, or SCHEDULE
).
In this case, the Max8HoursPerDayFullTime
rule specifies minutesWorkedMax
is 480
or 8 hours for each DAY
period.
Max40HoursPerWeekFullTime
specifies the minutesWorkedMax
is 2400
or 40 hours for each WEEK
period.
satisfiability
is optional and REQUIRED
by default if omitted.
With a satisfiability of REQUIRED
, breaking the rule would break a hard constraint and result in a hard penalty being applied to the solution score.
With a satisfiability of PREFERRED
, breaking the rule would break a soft constraint and result in a soft penalty being applied to the solution score.
By default, this type of rule only counts shifts if the shifts started within the defined period. If you define a rule that says that an employee can only work 1 shift a day, then it won’t count a night shift that started on the previous day but ends at e.g. 6 a.m. This means that the solver will still try to assign a shift to this day. To change this behavior change the field periodShiftOverlapKind
from START_ONLY
to START_AND_END
.
It is necessary to specify rules that apply to both the day and the week. If a rule for the week is included that sets a maximum number of hours per week at 40, but no rule is included for the day period, an employee could be assigned continuous shifts for 40 hours and then nothing for the rest of the week. Similarly, if a rule is included that sets a maximum number of hours per day at 8, but no rule is included for the week, an employee could be assigned an eight-hour shift every single day with no days off. |
When there are not enough employees to cover all the shifts, some shifts will be left unassigned.
Learn about the hard, medium, and soft constraints in Employee Shift Scheduling model. |
-
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": "Contract period rules example"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "Max8HoursPerDayFullTime",
"period": "DAY",
"minutesWorkedMax": 480
},
{
"id": "Max40HoursPerWeekFullTime",
"period": "WEEK",
"minutesWorkedMax": 2400
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon night",
"start": "2027-02-01T01:00:00Z",
"end": "2027-02-01T09:00:00Z"
},
{
"id": "Mon day",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Tue night",
"start": "2027-02-02T01:00:00Z",
"end": "2027-02-02T09:00:00Z"
},
{
"id": "Tue day",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "Wed night",
"start": "2027-02-03T01:00:00Z",
"end": "2027-02-03T09:00:00Z"
},
{
"id": "Wed day",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
},
{
"id": "Thu night",
"start": "2027-02-04T01:00:00Z",
"end": "2027-02-04T09:00:00Z"
},
{
"id": "Thu day",
"start": "2027-02-04T09:00:00Z",
"end": "2027-02-04T17:00:00Z"
},
{
"id": "Fri night",
"start": "2027-02-05T01:00:00Z",
"end": "2027-02-05T09:00:00Z"
},
{
"id": "Fri day",
"start": "2027-02-05T09:00:00Z",
"end": "2027-02-05T17:00:00Z"
},
{
"id": "Sat night",
"start": "2027-02-06T01:00:00Z",
"end": "2027-02-06T09:00:00Z"
},
{
"id": "Sat day",
"start": "2027-02-06T09:00:00Z",
"end": "2027-02-06T17: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": "Contract period rules example",
"submitDateTime": "2024-10-21T09:27:52.885391038Z",
"startDateTime": "2024-10-21T09:28:02.300826712Z",
"activeDateTime": "2024-10-21T09:28:02.400826712Z",
"completeDateTime": "2024-10-21T09:33:02.48500359Z",
"shutdownDateTime": "2024-10-21T09:33:02.58500359Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-7medium/0soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon night",
"employee": "Ann"
},
{
"id": "Mon day",
"employee": null
},
{
"id": "Tue night",
"employee": "Ann"
},
{
"id": "Tue day",
"employee": null
},
{
"id": "Wed night",
"employee": "Ann"
},
{
"id": "Wed day",
"employee": null
},
{
"id": "Thu night",
"employee": "Ann"
},
{
"id": "Thu day",
"employee": null
},
{
"id": "Fri night",
"employee": "Ann"
},
{
"id": "Fri day",
"employee": null
},
{
"id": "Sat night",
"employee": null
},
{
"id": "Sat day",
"employee": null
}
]
},
"kpis": {
"assignedShifts": 5,
"unassignedShifts": 7,
"workingTimeFairnessPercentage": 0.0,
"disruptionPercentage": 0.0
}
}
modelOutput
contains the employee schedule.
Ann has been assigned 5 shifts with 8 hours per day with a total of 40 hours for the week.
Some shifts have been unassigned.
Shift times use ISO 8601 date time with offset to UTC format. |
2.1. Filtering period rules with tags
Tags can be added to periodRules
and shifts
to control which shifts the period rules apply to.
For instance, if the maximum number of hours per week doesn’t apply to a specific department, the department can be excluded from the rule by adding excludeShiftTags
to the rule and referencing the tag that identifies the department’s shifts:
{
"periodRules": [
{
"id": "Max40HoursPerWeekFullTime",
"period": "WEEK",
"minutesWorkedMax": 2400,
"excludeShiftTags": [
"department A"
],
"shiftTagMatches": "ALL"
}
],
"shifts": [
{
"id": "Mon night",
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-01T08:00:00Z",
"tags": [
"department A"
]
}
]
}
Alternatively, if the rule only applies to a specific department, you can add includeShiftTags
to apply the rule to shifts with the tag.
includeShiftTags and excludeShiftTags can both be used in the same dataset, however, they cannot be used in combination for the same rule.
|
If shifts have multiple tags, you decide whether to match ALL of the tags or ANY of the tags based on the period rule by including shiftTagMatches
and setting it to ALL
or ANY
. If omitted, the default is ALL
.
In the following example, department A is understaffed and has decided to let employees work more than the normal 40 hours per week, however, they don’t want employees to work more than eight hours per day.
Department A is excluded from the max40HoursPerWeekFullTime
rule, and Ann is assigned 6 eight-hour shifts for a total of 48 hours in the week.
-
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": "Contract period rules example with tags"
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"periodRules": [
{
"id": "Max8HoursPerDayFullTime",
"period": "DAY",
"minutesWorkedMax": 480
},
{
"id": "Max40HoursPerWeekFullTime",
"period": "WEEK",
"minutesWorkedMax": 2400,
"excludeShiftTags":
[
"department A"
],
"shiftTagMatches": "ALL"
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon night",
"start": "2027-02-01T01:00:00Z",
"end": "2027-02-01T09:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Mon day",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Tue night",
"start": "2027-02-02T01:00:00Z",
"end": "2027-02-02T09:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Tue day",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Wed night",
"start": "2027-02-03T01:00:00Z",
"end": "2027-02-03T09:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Wed day",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Thu night",
"start": "2027-02-04T01:00:00Z",
"end": "2027-02-04T09:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Thu day",
"start": "2027-02-04T09:00:00Z",
"end": "2027-02-04T17:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Fri night",
"start": "2027-02-05T01:00:00Z",
"end": "2027-02-05T09:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Fri day",
"start": "2027-02-05T09:00:00Z",
"end": "2027-02-05T17:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Sat night",
"start": "2027-02-06T01:00:00Z",
"end": "2027-02-06T09:00:00Z",
"tags": [
"department A"
]
},
{
"id": "Sat day",
"start": "2027-02-06T09:00:00Z",
"end": "2027-02-06T17:00:00Z",
"tags": [
"department A"
]
}
]
}
}
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": "Contract period rules example with tags",
"submitDateTime": "2024-10-21T09:34:38.882106303Z",
"startDateTime": "2024-10-21T09:34:43.750759794Z",
"activeDateTime": "2024-10-21T09:34:43.850759794Z",
"completeDateTime": "2024-10-21T09:39:43.977385095Z",
"shutdownDateTime": "2024-10-21T09:39:44.077385095Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-6medium/0soft",
"tags": null,
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon night",
"employee": "Ann"
},
{
"id": "Mon day",
"employee": null
},
{
"id": "Tue night",
"employee": "Ann"
},
{
"id": "Tue day",
"employee": null
},
{
"id": "Wed night",
"employee": "Ann"
},
{
"id": "Wed day",
"employee": null
},
{
"id": "Thu night",
"employee": "Ann"
},
{
"id": "Thu day",
"employee": null
},
{
"id": "Fri night",
"employee": "Ann"
},
{
"id": "Fri day",
"employee": null
},
{
"id": "Sat night",
"employee": "Ann"
},
{
"id": "Sat day",
"employee": null
}
]
},
"kpis": {
"assignedShifts": 6,
"unassignedShifts": 6,
"workingTimeFairnessPercentage": 0.0,
"disruptionPercentage": 0.0
}
}
modelOutput
contains the employee schedule with Ann scheduled to work 6 eight-hour days for a total of 48 hours that week.
2.2. Logged time and period rules
Sometimes, a shift might include unpaid breaks that do not count toward the limit stipulated by a period rule. It is not practical to divide the shift into smaller shifts to account for the breaks.
Setting a shift loggedTime
attribute makes it possible to differentiate between the scheduled time, determined by shift start
and end
, and the actual working time, specified by loggedTime
.
For instance, consider the following two rules:
-
Max8LoggedHoursPerDayFullTime
limiting the number of shift logged time to 8 hours a day. -
Max10ScheduledHoursPerDayFullTime
limiting the number of scheduled work hours to 10 hours a day.
{
"periodRules": [
{
"id": "Max8LoggedHoursPerDayFullTime",
"period": "DAY",
"minutesLoggedMax": 480
},
{
"id": "Max10ScheduledHoursPerDayFullTime",
"period": "DAY",
"minutesWorkedMax": 600
}
],
"shifts": [
{
"id": "Mon night",
"start": "2027-02-01T00:00:00Z",
"end": "2027-02-01T08:00:00Z",
"loggedTime": "PT6H30M"
}
]
}
When assigned to an employee with the above period rules, the Mon night
shift contributes to the limits as follows:
-
6 hours and 30 minutes to
Max8LoggedHoursPerDayFullTime
. -
8 hours to
Max10ScheduledHoursPerDayFullTime
.
3. Combining contract rules example
Contract rules provide the most control over the schedules when they are used in combination with each other.
For instance, in the consecutive days worked rule example, Beth worked 3 consecutive twelve-hour shifts. If we don’t also define a period rule for the number of hours per week, Beth could be assigned a twelve-hour shift on Monday, Tuesday, and Wednesday, have no shift assigned on Thursday, and then be assign three more consecutive shifts on Friday, Saturday, and Sunday. If the intention is for Beth to work 3 consecutive shifts per week, we also need to include a period rule to prevent more shifts being assigned after those three days and the following day.
The following example defines a contract with the following rules:
-
Employees work a maximum of 8 hours per day.
-
Employees work a maximum of 40 hours per week.
-
Employees work a maximum of 5 consecutive days.
-
Employees have 12 hours between 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": "Contract shift times example",
"tags": []
}
},
"modelInput": {
"contracts": [
{
"id": "fullTimeContract",
"consecutiveDaysWorkedRules": [
{
"id": "Max5ConsecutiveDaysFullTime",
"maximum": 5
}
],
"periodRules": [
{
"id": "Max8HoursPerDayFullTime",
"period": "DAY",
"minutesWorkedMax": 480
},
{
"id": "Max40HoursPerWeekFullTime",
"period": "WEEK",
"minutesWorkedMax": 2400
}
],
"minutesBetweenShiftsRules": [
{
"id": "Minimum12HoursBetweenShiftsFullTime",
"minimumMinutesBetweenShifts": 720
}
]
}
],
"employees": [
{
"id": "Ann",
"contracts": [
"fullTimeContract"
]
}
],
"shifts": [
{
"id": "Mon night",
"start": "2027-02-01T01:00:00Z",
"end": "2027-02-01T09:00:00Z"
},
{
"id": "Mon day",
"start": "2027-02-01T09:00:00Z",
"end": "2027-02-01T17:00:00Z"
},
{
"id": "Tue night",
"start": "2027-02-02T01:00:00Z",
"end": "2027-02-02T09:00:00Z"
},
{
"id": "Tue day",
"start": "2027-02-02T09:00:00Z",
"end": "2027-02-02T17:00:00Z"
},
{
"id": "Wed night",
"start": "2027-02-03T01:00:00Z",
"end": "2027-02-03T09:00:00Z"
},
{
"id": "Wed day",
"start": "2027-02-03T09:00:00Z",
"end": "2027-02-03T17:00:00Z"
},
{
"id": "Thu night",
"start": "2027-02-04T01:00:00Z",
"end": "2027-02-04T09:00:00Z"
},
{
"id": "Thu day",
"start": "2027-02-04T09:00:00Z",
"end": "2027-02-04T17:00:00Z"
},
{
"id": "Fri night",
"start": "2027-02-05T01:00:00Z",
"end": "2027-02-05T09:00:00Z"
},
{
"id": "Fri day",
"start": "2027-02-05T09:00:00Z",
"end": "2027-02-05T17:00:00Z"
},
{
"id": "Sat night",
"start": "2027-02-06T01:00:00Z",
"end": "2027-02-06T09:00:00Z"
},
{
"id": "Sat day",
"start": "2027-02-06T09:00:00Z",
"end": "2027-02-06T17: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": "Contract shift times example",
"submitDateTime": "2024-10-21T10:05:02.930407315Z",
"startDateTime": "2024-10-21T10:05:07.964859924Z",
"activeDateTime": "2024-10-21T10:05:08.064859924Z",
"completeDateTime": "2024-10-21T10:10:08.215530452Z",
"shutdownDateTime": "2024-10-21T10:10:08.315530452Z",
"solverStatus": "SOLVING_COMPLETED",
"score": "0hard/-7medium/0soft",
"tags": [],
"validationResult": {
"summary": "OK"
}
},
"modelOutput": {
"shifts": [
{
"id": "Mon night",
"employee": "Ann"
},
{
"id": "Mon day",
"employee": null
},
{
"id": "Tue night",
"employee": "Ann"
},
{
"id": "Tue day",
"employee": null
},
{
"id": "Wed night",
"employee": "Ann"
},
{
"id": "Wed day",
"employee": null
},
{
"id": "Thu night",
"employee": "Ann"
},
{
"id": "Thu day",
"employee": null
},
{
"id": "Fri night",
"employee": "Ann"
},
{
"id": "Fri day",
"employee": null
},
{
"id": "Sat night",
"employee": null
},
{
"id": "Sat day",
"employee": null
}
]
},
"kpis": {
"assignedShifts": 5,
"unassignedShifts": 7,
"workingTimeFairnessPercentage": 0.0,
"disruptionPercentage": 0.0
}
}
modelOutput
contains the employee schedule which conforms with the contract rules in the modelInput.
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.
-
Working with Employee availability.