Skip to content
Blog

From complex labor laws to scalable conditional constraints


Our engineer Lukas Downes shares his experience building constraints which don’t always apply.

I like labor laws, but holy cats, they're complex to implement. The people who formulated the labor laws wanted to ensure the well-being of employees, but forgot about the poor engineers who have to implement them.

Take a look at this scheduling constraint for example:

An employee must work a maximum of 40h every 7 days, if they worked a night shift.

This is what we call a (headache) Conditional Constraint. Let me show you how I solved it.

# Dealing with conditional constraints

The first step is to understand the requirement. "Every seven days" means that I need to group shifts in 7 day windows we call these “rolling windows”. "If they work a night shift" means that I need to count the night shifts in this group of shifts. "Work a Maximum of 40h" is what I need to evaluate and penalize in that same group of shifts, if I detected a night shift.

Next, I want to lay out how I want my constraint to look.

  1. I need to count the amount of shifts worked in a rolling window
  2. I also need to track the minutes worked in that same rolling window
  3. I want to filter shifts with certain tags, but independently for both previous rules, i.e. we want to count night shifts, and penalise the time worked of all shifts
  4. Everything should be high performance and scale up to at least 100.000 shifts
  5. The API should be minimal, clear, extensible, specific...

With the rules set, I can start working on this. Grouping the shifts for (1.) and (2.) can be difficult, but luckily I can just do a cheeky copy paste from our existing constraints. Combining them can also be pretty challenging, because I have to penalize a group of shifts, if the same group of shifts exists in the other stream. 

But the answer lies in how you formulate the problem. "... if ... exists ..." is the perfect case to use the constraint node ifExists. A benefit of using ifExists in this case, is that we ensure scalability and performance.

# API design

Another difficult step is creating an easily extensible, specific, easily understandable API. During our design meeting, we settled on:

{
 "rollingWindowRules": {
   "id": "max 36h worked if night shift is present",
   "window": 0,
   "minutesWorkedLimit": {
     "minutesWorkedMax": 2160 // 36 * 60 minutes
   },
   "condition": {
     "triggerLimit": 1,
     "type": "MIN_SHIFTS_WORKED",
     "includeShiftTags": [ "night shift" ],
     "shiftTagMatches": "ALL"
   }
 }
}

Let's see if it meets our requirements. 

  1. minimal: yes, we only added the 'condition' field.
  2. extensible: yes, a type to specify what type of condition the triggerLimit will represent, for example MIN_SHIFTS_WORKED or MIN_MINUTES_WORKED. I only implemented these two, but we can easily add more.
  3. specific: yes, we allow the filtering of shifts with our includeShiftTags.

# Conclusion

I'm proud of how the constraint turned out. I managed to create a design for a complex problem that makes full use of the constraint stream's performance capabilities and is still readable and extensible. Creating new conditions is super easy now. So if you're interested in having a condition like "if the time off exceeds 48h then ..." or "if the amount of consecutive shifts worked exceeds 3 then..." Please do contact us, and I'll be so glad to put my hard work to good use!

Continue reading