Docs
  • Solver
  • Models
    • Field Service Routing
    • Employee Shift Scheduling
    • Pick-up and Delivery Routing
    • Task Scheduling
  • Platform
Start Free Trial
  • Employee Shift Scheduling
  • The basics
  • Lessons overview
  • Lesson 6
  • latest
    • latest
    • 1.37.x
    • 1.36.x

Employee Shift Scheduling

  • The basics
    • Introduction
    • Getting started: Hello world
    • Lessons overview
      • Lesson 1
      • Lesson 2
      • Lesson 3
      • Lesson 4
      • Interlude 1: Basic demo data
      • Lesson 5
      • Lesson 6
      • Lesson 7
    • User guide
      • Terminology
      • Use case guide
      • Scheduling API concepts
      • Integration
      • Constraints
      • Using the API
        • Using the OpenAPI spec
        • API tooling
      • Demo datasets
      • Input datasets
        • Model configuration
        • Model input
        • Planning window
      • Planning window
      • Time zones and Daylight Saving Time (DST)
      • Tags and tag types
      • Input validation
      • Output datasets
        • Metadata
        • Model output
        • Input metrics
        • Key performance indicators (KPIs)
      • Metrics and optimization goals
      • Score analysis
      • Visualization
      • Efficiency X-Ray
      • Assignability analysis
  • Feature guides
    • Employee resource constraints
      • Employee availability and preferences
        • Employee availability
        • Employee preferences
      • Employee contracts
      • Employee priority
      • Pairing employees
      • Employee groups
      • Shift travel and locations
      • Shift Breaks
      • Employee activation
      • Work limits
        • Minutes worked per period
        • Minutes worked in a rolling window
        • Minutes logged per period
        • Days worked per period
        • Days worked in a rolling window
        • Consecutive days worked
        • Shifts worked per period
        • Shifts worked in a rolling window
        • Weekend minutes worked per period
        • Weekends worked per period
        • Weekends worked in a rolling window
        • Consecutive weekends worked
        • Consecutive shifts worked
      • Time off
        • Days off per period
        • Consecutive days off per period
        • Consecutive days off in a rolling window
        • Consecutive minutes off in a rolling window
        • Shifts to avoid close to day off requests
        • Consecutive weekends off per period
      • Shift rotations and patterns
        • Shift rotations
        • Single day shift sequence patterns
        • Minimize gaps between shifts
        • Multi-day shift sequence patterns
        • Daily shift pairings
        • Overlapping shifts
        • Shift start times differences
        • Minutes between shifts
      • Shift type diversity
        • Shift tag types
        • Shift types worked per period
        • Unique tags per employee per period
      • Fairness
        • Balance time worked
        • Balance shift count
    • Shift service constraints
      • Alternative shifts
      • Cost management
        • Cost groups
        • Employee rates
      • Demand-based scheduling
      • Priority and optional shifts
      • Skills and risk factors
      • Shift assignments
        • Shift selection
        • Employee selection
    • Scenarios
      • Configuring labor law compliance
      • Configuring employee well-being
      • Self-rostering and optimization
  • Responding to change
    • Manual intervention
    • Recommendations
    • Real-time planning
  • More information
    • FAQs
    • Changelog
    • Upgrade to the latest version
    • Feature requests
Ask Claude Ask ChatGPT

Lesson 6: Treating shifts differently

Introduction

So far, all the rules that we have covered assume that all shifts are the same. They apply across the board to every shift. In this lesson, we will cover how to implement rules that do not apply to all shifts, but instead to a subset of shifts. This will allow you to express rules such as:

  • Maximum ten hours of hazardous shifts worked in one week.

  • Maximum five night shifts worked in four weeks.

  • Weekend shifts only count as weekend shifts if predefined, not according to calendar rules but by agreement of the manager or department.

We use tags to separate shifts in Timefold. All the period and rolling window rules that were covered previously can use for tags. In this lesson we learn how to implement tags.

Tags

Tags are labels which can be applied to shifts and employees. Similarly to skills, tags can be any string, including coded or anonymized strings which do not represent real words in any language.

It is a good idea to pause here briefly and think of all the different kinds of shifts and shift categories that your organization uses. Some examples could be on-call shifts, night shifts, split shifts, short shifts, or over-long shifts. Gaining a deep understanding of your specific scheduling problem is very valuable before trying to convert it into Timefold constraints.

To add tags to a shift, we add the tags JSON field:

{
  "shifts": [
    {
      "id": "Mon Night Bakery shift",
      "start": "2027-02-01T00:00:00Z",
      "end": "2027-02-01T08:00:00Z",
      "tags": [
        "Location 24601",
        "8 hours long"
      ]
    }
  ]
}

Generally, shift tags are more widely used than employee tags. You can add employee tags in a similar way, by adding the tags JSON field:

{
  "id": "Beth",
  "tags": [
    "Over-16",
    "Supervisor"
  ]
}
By themselves, tags do not do anything. They are just labels. They need to be used in rules in order to have an effect on the schedule.

Tag types

Tag types are categories that tags belong to. Each tag can belong to exactly one category. Most rules do not require you to specify the tag’s type. It is enough to directly assign the tag to a shift or employee, and it will be automatically created by Timefold. However, some rules require tag types to be specified and tags to be pre-created before assignment in order to function correctly. To specify tag types and tags, we must create another two top-level fields in the JSON file, on the same level as employees and shifts:

{
  "modelInput": {
    "tagTypes": [
      {
        "id": "Locations"
      }
    ],
    "tags": [
      {
        "id": "Location 3",
        "tagType": "Locations"
      },
      {
        "id": "Location 759",
        "tagType": "Locations"
      },
      {
        "id": "Location 24601",
        "tagType": "Locations"
      }
    ]
  }
}
Each declared tag type must be referenced by at least one tag, otherwise the dataset will fail validation. Similarly, each tag type used by a tag must be declared in tagTypes, otherwise the dataset will fail validation.

Tag types are required for the following rules, which are not covered in this lesson but can be explored in the technical documentation:

  • Shift types worked per period

  • Employee groups assigned to a commonTagType

  • Shift rotations

  • Shift travel: exclude a certain type of shift from the rule

  • Unique tags per employee per period

  • Consecutive days worked and shift sequences

  • Preferred and required shift tags

  • Global unique shift tags worked per period

  • Consecutive days worked with any one of minDelayBetweenDifferentSequences, minDurationBetweenDifferentSequences, allowedSequenceStartDays, or rewardCompactSequences

Filtering by tags in rules

Specifying the tag list

The most common use of tags is to filter shifts in rules. We have two ways of specifying the list of tags that should be filtered, includeShiftTags and excludeShiftTags:

  • includeShiftTags specifies that the rule only applies to shifts which match the specified tags. Any other shifts are not counted toward the rule, and do not contribute to any penalties or rewards.

  • excludeShiftTags specifies that the rule only applies to shifts which do not match the specified tags. Any other shifts are not counted toward the rule, and do not contribute to any penalties or rewards.

You can think of these fields as splitting the dataset of shifts into two groups: those that match the rule and those that do not. The rule only applies to the matching group, and the other group is ignored. They are functionally equivalent, because you can either:

  • specify the group that matches, and the non-matching group is automatically determined.

  • or specify the group that does not match, and the matching group is automatically determined.

Specifying the tag list: the rule applies to the shifts tagged Night and ignores the shifts tagged Day

Because they are equivalent, you can only use one of includeShiftTags or excludeShiftTags in a rule. If you use both, the dataset will fail validation.

Matching the tag list

There are two ways of matching tags for filtering, specified by shiftTagMatches:

  • shiftTagMatches set to ANY means that a shift is included or excluded if it has at least one of the specified tags.

  • shiftTagMatches set to ALL means that a shift is included or excluded if it has all the specified tags. It can have other tags as well.

shiftTagMatches is optional. The default behavior is ALL when not specified.

Using tags with a rule

Let’s assume that there are three different active rules for the maximum number of consecutive days worked:

  • Maximum six days in a row at any time.

  • Maximum five days in a row if no shifts worked are shorter than six hours (shifts are tagged Short).

  • Maximum three days in a row if all the shifts worked are hazardous or night shifts (shifts are tagged Hazardous or Night).

We can implement this by creating three different consecutive days worked rules in the same contract:

  • A limit of six days in a row for all shifts.

  • A limit of five days in a row which excludes Short shifts.

  • A limit of three days in a row which only includes Hazardous and Night shifts.

{
  "contracts": [
    {
      "id": "MaxConsecutiveDaysWorkedContract",
      "consecutiveDaysWorkedRules": [
        {
          "id": "Max6ConsecutiveDaysWorked",
          "maximum": 6,
          "satisfiability": "REQUIRED"
        },
        {
          "id": "Max5ConsecutiveDaysWorked_ifNoShortShifts",
          "maximum": 5,
          "satisfiability": "REQUIRED",
          "excludeShiftTags": [
            "Short"
          ]
        },
        {
          "id": "Max3ConsecutiveDaysWorked_hazardousOrNightShiftsOnly",
          "maximum": 3,
          "satisfiability": "REQUIRED",
          "includeShiftTags": [
            "Night",
            "Hazardous"
          ],
          "shiftTagMatches": "ANY"
        }
      ]
    }
  ]
}

These three rules are all active and the schedule must obey all three rules at the same time.

Practical example

This example has one employee, Beth, who is subject to the rules above. The schedule has seven shifts, one on each day of the week, each shift is eight hours long with the exception of the Sunday shift which is five hours long and tagged Short. The Wednesday shift is a Night shift and the Thursday shift is Hazardous. The Saturday shift stays unassigned because Beth works the maximum of five consecutive days with no Short shifts. The Sunday shift is assigned because, with Saturday unassigned, Sunday doesn’t continue the run of consecutive days, so the count resets.”

Tags and tag matching: Beth works the Night
  • Input

  • Output

Try this example in Timefold Platform by saving this JSON into a file called sample.json and making 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": "Lesson 6: Tags and tag matching",
      "tags": []
    },
    "model": {}
  },
  "modelInput": {
    "contracts": [
      {
        "id": "MaxConsecutiveDaysWorkedContract",
        "consecutiveDaysWorkedRules": [
          {
            "id": "Max6ConsecutiveDaysWorked",
            "maximum": 6,
            "satisfiability": "REQUIRED"
          },
          {
            "id": "Max5ConsecutiveDaysWorked_ifNoShortShifts",
            "maximum": 5,
            "satisfiability": "REQUIRED",
            "excludeShiftTags": [
              "Short"
            ]
          },
          {
            "id": "Max3ConsecutiveDaysWorked_hazardousOrNightShiftsOnly",
            "maximum": 3,
            "satisfiability": "REQUIRED",
            "includeShiftTags": [
              "Night",
              "Hazardous"
            ],
            "shiftTagMatches": "ANY"
          }
        ]
      }
    ],
    "employees": [
      {
        "id": "Beth Fox",
        "contracts": [
          "MaxConsecutiveDaysWorkedContract"
        ]
      }
    ],
    "shifts": [
      {
        "id": "Mon",
        "start": "2027-02-01T06:00:00-04:00",
        "end": "2027-02-01T14:00:00-04:00"
      },
      {
        "id": "Tue",
        "start": "2027-02-02T06:00:00-04:00",
        "end": "2027-02-02T14:00:00-04:00"
      },
      {
        "id": "Wed Night",
        "start": "2027-02-03T01:00:00-04:00",
        "end": "2027-02-03T09:00:00-04:00",
        "tags": [
          "Night"
        ]
      },
      {
        "id": "Thu Hazardous",
        "start": "2027-02-04T06:00:00-04:00",
        "end": "2027-02-04T14:00:00-04:00",
        "tags": [
          "Hazardous"
        ]
      },
      {
        "id": "Fri",
        "start": "2027-02-05T06:00:00-04:00",
        "end": "2027-02-05T14:00:00-04:00"
      },
      {
        "id": "Sat",
        "start": "2027-02-06T06:00:00-04:00",
        "end": "2027-02-06T14:00:00-04:00"
      },
      {
        "id": "Sun Short",
        "start": "2027-02-07T07:00:00-04:00",
        "end": "2027-02-07T12:00:00-04:00",
        "tags": [
          "Short"
        ]
      }
    ],
    "globalRules": {}
  }
}
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>
{
  "metadata": {
    "id": "ID",
    "originId": "ID",
    "name": "Lesson 6: Tags and tag matching",
    "submitDateTime": "2026-10-07T14:53:50.754063981Z",
    "startDateTime": "2026-10-07T14:54:00.698015475Z",
    "activeDateTime": "2026-10-07T14:54:00.824795503Z",
    "completeDateTime": "2026-10-07T14:54:31.305381869Z",
    "shutdownDateTime": "2026-10-07T14:54:31.305385365Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/-1medium/0soft",
    "tags": [
      "system.type:from-request",
      "system.profile:Standard profile"
    ],
    "validationResult": {
      "summary": "OK"
    }
  },
  "modelOutput": {
    "shifts": [
      {
        "id": "Mon",
        "employee": "Beth Fox"
      },
      {
        "id": "Tue",
        "employee": "Beth Fox"
      },
      {
        "id": "Wed Night",
        "employee": "Beth Fox"
      },
      {
        "id": "Thu Hazardous",
        "employee": "Beth Fox"
      },
      {
        "id": "Fri",
        "employee": "Beth Fox"
      },
      {
        "id": "Sat"
      },
      {
        "id": "Sun Short",
        "employee": "Beth Fox"
      }
    ],
    "employees": [
      {
        "id": "Beth Fox",
        "metrics": {
          "assignedShifts": 6,
          "durationWorked": "PT45H"
        }
      }
    ],
    "generatedShifts": []
  },
  "inputMetrics": {
    "employees": 1,
    "shifts": 7,
    "pinnedShifts": 0,
    "mandatoryShifts": 7,
    "optionalShifts": 0
  },
  "kpis": {
    "assignedShifts": 6,
    "unassignedShifts": 1,
    "disruptionPercentage": 0,
    "activatedEmployees": 1,
    "assignedMandatoryShifts": 6
  },
  "run": {
    "id": "ID",
    "originId": "ID",
    "name": "Lesson 6: Tags and tag matching",
    "submitDateTime": "2026-10-07T14:53:50.754063981Z",
    "startDateTime": "2026-10-07T14:54:00.698015475Z",
    "activeDateTime": "2026-10-07T14:54:00.824795503Z",
    "completeDateTime": "2026-10-07T14:54:31.305381869Z",
    "shutdownDateTime": "2026-10-07T14:54:31.305385365Z",
    "solverStatus": "SOLVING_COMPLETED",
    "score": "0hard/-1medium/0soft",
    "tags": [
      "system.type:from-request",
      "system.profile:Standard profile"
    ],
    "validationResult": {
      "summary": "OK"
    }
  }
}

modelOutput contains the schedule with Beth assigned to five shifts from Monday to Friday and also the Sunday shift, which gives a score of 0hard/-1medium/0soft.

Explore what happens when the Friday shift is tagged Short or Hazardous, or both. Do this by changing the input to add the tag(s) to the shift, and re-running the input.

Conclusions

In this lesson, we covered:

  • How to add tags to shifts and employees in the dataset.

  • How to create tag types and assign tags to them.

  • How to filter shifts in rules using includeShiftTags and excludeShiftTags.

  • How to match tags using shiftTagMatches set to ANY or ALL.

  • How to implement multiple rules for the same constraint, each with different tag filters, to express complex scheduling requirements.

Next

  • Proceed to Lesson 7 to learn about planning around employee absences.

  • Discover more information in the technical documentation on tags and tag types.

  • Discover information on including and excluding shift tags in the technical documentation for each constraint, for example minutes worked per period.

  • Go back to the Lessons overview to choose a different lesson to continue with.

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