Shift Scheduling
Creating shifts and assigning them to employees in 1 go.
Creating an optimal roster for a team of employees based on forecasted demand and taking into account extensive labor agreement rules & constraints.
- Based on last weeks sales, last planning, predicted weather, revenue and reservations, a clear demand can be generated used as base occupation per skill, per location, per time block.
- Collective Work Agreements are numerous and complex. Our optimisation already takes into account hundreds of specific employee constraints. Not enough? Simply create your own work rules.
- Not only automagically generate a plan in seconds, but define what is important in the optimisation. Is financial wage cost minimisation more important than fairness? It's just a parameter. Our advanced mathematical solver engine can handle any request.
Input
Shift scheduling is about fairly distributing shifts among employees according to capacity requirements, specialism constraints, preferences, shift types and CAO agreements.
{
"solver": "SHIFT",
"demand": [ ... ],
"employees": [ ... ],
"options": {
"shiftBlocksPerDay": 24
}
}
The structure of a basic workforce planning model is as follows:
Property | Value |
---|---|
demand | Array of Shift Demands |
statutes | Array of Statutes |
employees | Array of Employees |
options | A list of options for the scheduling problem |
Shift Demands
{
"shift": {
...
},
"skill": "Kitchen"
"prefDemand": 2,
"minDemand": 1,
"maxDemand": 3
}
The list of shift demands represents the workstation/skill shift blocks to which it is required/necessary to assign (an) employee(s).
Property | Value | Description | Required |
---|---|---|---|
shift | Shift Block | the shift block object | Required |
skill | String | the skill/workstation | Required |
prefDemand | Integer | the preferred demand for that shift block | Required |
minDemand | integer | the minimal demand for that shift block | Optional |
maxDemand | integer | the maximal demand for that shift block | Optional |
Shift Blocks
{
"date": "2018-11-14",
"index": 0,
}
A shift block object consists of a date and a unique index for that block on that date. In combination with the number of shift blocks per day (see: Options), the user has full control of the detail/granularity of the demand profile that can be modeled. Please note, that an index larger than shiftBlocksPerDay can be provided: to model demand from the day after that should logically by filled in with shifts starting this day. E.g. an index 26 (with "shiftBlocksPerDay": 24) represents 01:00 of the following day.
Property | Value | Description | Required |
---|---|---|---|
date | String | the date of the shift block (in "YYYY-MM-DD") | Required |
index | Integer | the index of the shift block | Required |
Statutes
{
"name": "FIX",
"minShiftLength": 3,
"maxShiftLength": 11,
"minRestBetweenShifts": 12,
"maxConsecutiveWorkDays": 6,
"maxHrsPerWeek": 50,
"minHrsPerWeek": 10,
"shiftStart": 0.0,
"latestShiftStart": 24.0,
"shiftEnd": 22.0,
"minHoursMonth": 100,
"maxHoursMonth": 200,
"overTimeThreshold1": 9.0,
"overTimeRaise1": 1.2,
"overTimeThreshold2": 11.0,
"overTimeRaise2": 1.5,
"overTimeWeeklyThreshold1": 38,
"overTimeWeeklyRaise1": 1.5,
"nightlyThreshold1": 20.0,
"nightlyRaise": 1.5
}
The list of statutes contain CAO specific parameters and the information with respect to the renumeration of the employees.
Property | Value | Description | Required |
---|---|---|---|
name | String | the name of a statues: e.g. "FIX", "PARTTIME", "STUDENT", ... | Required |
minShiftLength | Integer | The minimum number of hours in a shift for an employee | Required |
maxShiftLength | Integer | The maximum number of hours in a shift for this employee | Required |
minRestBetweenShifts | Integer | The minimum duration (in hours) of a rest period between two shifts for this employee | Required |
maxConsecutiveWorkDays | Integer | The maximum days consecutive that this employee can be working | Required |
maxHrsPerWeek | Integer | The maximum number of hours this employee can be working in a week | Required |
minHrsPerWeek | Integer | The minimum number of hours this employee should be working in a week | Required |
shiftStart | Double | The earliest this employee can start working (expressed as decimal in 24 hour-format) | Required |
latestShiftStart | Double | The latest this employee can start working (expressed as decimal in 24 hour-format) | Required |
shiftEnd | Double | The latest this employee can work on a day (expressed as decimal in 24 hour-format) | Required |
minHoursMonth | Integer | The minimum hours per month this employee should be working | Required |
maxHoursMonth | Integer | The maximum hours per month this employee should be working | Required |
overTimeThreshold1 | Double | The first time threshold for overtime (expressed as decimal in 24 hour-format) | Required |
overTimeRaise1 | Double | The raise after the first time threshold for overtime (e.q. 1.2 means a 20% raise per hour) | Required |
overTimeThreshold2 | Double | The second time threshold for overtime (expressed as decimal in 24 hour-format) | Required |
overTimeRaise2 | Double | The raise after the second time threshold for overtime (e.q. 1.2 means a 20% raise per hour) | Required |
overTimeWeeklyThreshold1 | Integer | The number of hours per week after which a "weekly overtime" raise should be given | Required |
overTimeWeeklyRaise1 | Double | The weekly overtime raise (e.q. 1.2 means a 20% raise per hour) | Required |
nightlyThreshold1 | Double | The time threshold to be considered for a night raise (expressed as decimal in 24 hour-format) | Required |
nightlyRaise | Double | The raise after the first time threshold for overtime (e.q. 1.2 means a 20% raise per hour) | Required |
Employees
{
"name": "E0",
"statute": "FIX",
"skill": [
"Kitchen",
"Restaurant",
],
"hourlyPay": 11,
"efficiency": 1,
"preferences": [
{
"start": "2019-08-09T18:00:00",
"end": "2019-08-10T02:00:00"
}
],
"hoursWorkedThisMonth": 0,
"lastRestDate": "2019-03-04"
}
The employees to assign to the shift block demands are defined as follows.
Property | Value | Description | Required |
---|---|---|---|
name | String | Employee name or reference | Required |
statute | String | The statute of the employee | Required |
skill | Array of Strings | The skills/workstations this employee can man | Required |
hourlyPay | Double | The hourly wage for the employee | Required |
efficiency | Double | The efficiency of the employee: e.g. two new employees (efficiency: 0.5) are needed to replace an employee with more experience | Required |
preferences | Array of shift preference objects | The preferred working hours for this employee. | |
hoursWorkedThisMonth | Integer | The number of hours this employee has already worked this month | Required |
lastRestDate | Date | The date of last 36-hour rest period for this employee (in "YYYY-MM-DD") | Required |
Shift preferences
The shift preferences for the employees can be submitted using shift preference objects:
Property | Value | Description | Required |
---|---|---|---|
start | LocalDateTime | The start of the preferred shift | Required |
end | LocalDateTime | The end of the preferred shift | Required |
|
Assignments
Assignments can be submitted in the solve request to the workforceplanning model for different reasons:
- To fix certain pre-defined assignments (set "locked" : "true");
- To set holidays, days-off or sick leave for an employee (set "locked" : "true" and set the skill of this assignment to a skill for which there is no demand given in the model, e.g. "skill" : "Unavailable");
- To communicate the performed shifts of the last day of the previous planning period (i.e. last week);
{
"id": 1,
"date": "2019-03-07",
"employee": "E0",
"skill": "Restaurant",
"startDateTime": "2019-03-07T00:00:00",
"endDateTime": "2019-03-07T21:00:00",
"locked": true,
"countAsWorkingHours": false,
"costs": { ... }
}
Property | Value | Description | Required for Input |
---|---|---|---|
id | Integer | A unique identifier for the assignment | Required |
date | LocalDate | The date of the assignment. | Required |
employee | String | The name of the assigned employee | Required |
skill | String | The skill/workstation this employee is assigned to | Required |
start | Integer | The starting shiftblock | Required |
duration | Integer | The duration, expressed in shiftblocks | Required |
startDateTime | LocalDateTime | The starting hour for the assignment | Optional |
endDateTime | LocalDateTime | The end hour for the assignment | Optional |
locked | Boolean | If this assignment is locked for the optimization or not. | Optional |
countAsWorkingHours | Boolean | If this assignment should be included in working hours per week/ month etc. constraints. | Optional |
costs | Costs | The cost object associated with this assignment (see under) | Optional |
Solution
{
"E0": [
{
"date": "2018-11-14",
"index": 4
},
{
"date": "2018-10-14",
"index": 5
},
{
"date": "2018-10-14",
"index": 6
}
],
"E1": [
{
"date": "2018-11-14",
"index": 8
}
]
}
When the problem is solved, the solution object returns the assigned Timeblocks per employee.
The assignments can also be found in the ShiftAssignments object. The assignment submitted in the solve request (if any) will be added to the ones constructed to solve the problem.
Once solved, The following objects can also be retrieved from the Employees object:
- Hours
- Costs
In addition, an aggregate Costs object can also be retrieved from the json, combining the costs of all employees.
Hours
{
"hours": 12,
"nightTimeHours": 2.5,
"overTime1Hours": 0,
"overTime2Hours": 0,
"weeklyOverTimeHours": 0
}
Property | Value | Description |
---|---|---|
hours | Double | The total number of hours assigned to this employee this planning period |
nightTimeHours | Double | The number of hours, after the night threshold, assigned to this employee this planning period |
overTime1Hours | Double | The number of hours, after the first daily overtime threshold, assigned to this employee this planning period |
overTime2Hours | Double | The number of hours, after the second daily overtime threshold, assigned to this employee this planning period |
weeklyOverTimeHours | Double | The number of hours, after the weekly overtime threshold, assigned to this employee this planning period |
Costs
{
"hourCost": 132,
"nightTimeCost": 13.75,
"overTime1Cost": 0,
"overTime2Cost": 0,
"weeklyOverTimeCost": 0
}
Property | Value | Description |
---|---|---|
hourCost | Double | The hour cost for this employee this planning period (excl. overtime, nightime, ...) |
nightTimeCost | Double | The cost associated with the hours, after the night threshold, for this employee this planning period |
overTime1Cost | Double | The cost associated with the hours, after the first daily overtime threshold, for this employee this this planning period |
overTime2Cost | Double | The cost associated with the hours, after the second daily overtime threshold, for this employee this this planning period |
weeklyOverTimeCost | Double | The cost associated with the hours, after the weekly overtime threshold, for this employee this this planning period |
options
{
"shiftBlocksPerDay": 24,
"includeShiftPreferences:": false
}
The definition of the options.
Property | Value | Description | Required |
---|---|---|---|
shiftBlocksPerDay | Integer | The number of blocks in a day | Required |
includeShiftPreferences | Boolean | Whether the shift preferences should be accommodated maximally | optional |
startDate | Date | A reference date for this planning period | optional |
Updated over 3 years ago
Do you want to split up the phases "Shift Creation" and "Shift Filling", check out these solvers: