FAILURE MAP
← Case archive

FA-93911 / Shift rostering labor rules / Open access

One-hour meal gap counted as a split shift · case 01

A standard 60-minute unpaid break triggers a split premium.

Verified by executionVariant 1 · 8 checks per implementationDownload source bundle ↓JSON ↗

ROOT CAUSE

The split gap test is inclusive of 60 minutes.

VERIFIED REPAIR

Only gaps longer than 60 minutes are splits.

Unsuccessful approach: Measuring the gap from segment start to next start counts nearly every multi-segment day as split.

Case contract

Worked segments [start, end] of one workday (unordered, possibly overlapping) and a minimum wage in cents. Overlapping or touching segments merge. Spread is last end minus first start. Each gap longer than 60 minutes is a split. A spread over 600 minutes earns one hour of minimum wage and replaces any split premium; otherwise each split earns min_wage // 2, for at most two splits. Return [spread, splits, premium].

Why this case matters

Spread-of-hours and split-shift premiums are frequent roster-pay rules that depend on correct interval handling.

1 / The failure

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(segments, min_wage):
    seg = sorted(segments)
    merged = []
    for s, e in seg:
        if merged and s <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], e)
        else:
            merged.append([s, e])
    spread = merged[-1][1] - merged[0][0]
    splits = sum(1 for a, b in zip(merged, merged[1:]) if b[0] - a[1] >= 60)
    if spread > 600:
        premium = min_wage
    else:
        premium = (min_wage // 2) * min(splits, 2)
    return [spread, splits, premium]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: split gap threshold 1', [[[480, 720], [780, 1080]], 1600], [600, 0, 0]),
  ('regression variant: split gap threshold 2',
   [[[480, 540], [630, 870], [930, 1170], [1170, 1470], [490, 500]], 1701], [990, 1, 1701]),
  ('partial repair guard 3', [[[870, 1110], [1170, 1290], [480, 540], [720, 780]], 1655], [810, 2, 1655]),
  ('boundary control 4', [[[420, 1020]], 1600], [600, 0, 0]),
  ('boundary control 5', [[[420, 480], [550, 600], [700, 760], [840, 900], [980, 1010]], 1655],
   [590, 4, 1654]),
  ('normal control 6', [[[810, 870], [840, 1020], [600, 720], [990, 1290]], 1600], [690, 1, 1600]),
  ('normal control 7', [[[630, 690], [760, 770], [720, 780], [750, 810], [480, 660]], 1600], [330, 0, 0]),
  ('normal control 8', [[[601, 841], [480, 540], [1081, 1261], [931, 991]], 1701], [781, 3, 1701])],
 [('regression: split gap threshold 1', [[[420, 600], [660, 780], [840, 1020]], 1701], [600, 0, 0]),
  ('regression variant: split gap threshold 2', [[[899, 959], [420, 720], [779, 839]], 1701], [539, 0, 0]),
  ('partial repair guard 3', [[[610, 620], [360, 540], [600, 900]], 1655], [540, 0, 0]),
  ('boundary control 4', [[[900, 1000], [420, 600], [700, 800]], 1520], [580, 2, 1520]),
  ('boundary control 5', [[[480, 720], [781, 1080]], 1600], [600, 1, 800]),
  ('normal control 6', [[[450, 510], [360, 420], [370, 380]], 1520], [150, 0, 0]),
  ('normal control 7', [[[960, 1140], [420, 480], [480, 720]], 1520], [720, 1, 1520]),
  ('normal control 8', [[[420, 600], [659, 719], [749, 869]], 1655], [449, 0, 0])],
 [('regression: split gap threshold 1', [[[600, 780], [1139, 1379], [839, 1079]], 1701], [779, 0, 1701]),
  ('regression variant: split gap threshold 2', [[[1080, 1140], [420, 600], [660, 960], [1140, 1320]], 1600],
   [900, 1, 1600]),
  ('partial repair guard 3', [[[420, 480], [570, 870], [430, 440], [929, 1229]], 1655], [809, 1, 1655]),
  ('boundary control 4', [[[600, 630], [700, 730], [800, 830], [900, 930]], 1701], [330, 3, 1700]),
  ('boundary control 5', [[[420, 1021]], 1600], [601, 0, 1600]),
  ('normal control 6', [[[570, 810], [900, 1200], [910, 920], [420, 540]], 1701], [780, 1, 1701]),
  ('normal control 7', [[[420, 600], [840, 1080]], 1600], [660, 1, 1600]),
  ('normal control 8', [[[690, 750], [480, 720]], 1701], [270, 0, 0])],
 [('regression: split gap threshold 1', [[[1079, 1319], [1378, 1618], [720, 1020], [600, 660]], 1701],
   [1018, 0, 1701]),
  ('regression variant: split gap threshold 2', [[[1021, 1261], [781, 961], [480, 720]], 1600],
   [781, 1, 1600]),
  ('partial repair guard 3', [[[570, 810], [900, 1200], [910, 920], [420, 540]], 1701], [780, 1, 1701]),
  ('boundary control 4', [[[420, 600], [660, 780], [840, 1020]], 1701], [600, 0, 0]),
  ('boundary control 5', [[[420, 450], [520, 550], [620, 650], [720, 750]], 1600], [330, 3, 1600]),
  ('normal control 6', [[[750, 810], [900, 1140], [1110, 1230], [480, 780]], 1600], [750, 1, 1600]),
  ('normal control 7', [[[430, 440], [420, 720]], 1655], [300, 0, 0]),
  ('normal control 8', [[[480, 540], [630, 870], [930, 1170], [1170, 1470], [490, 500]], 1701],
   [990, 1, 1701])],
 [('regression: split gap threshold 1', [[[1110, 1410], [600, 900], [990, 1050], [1471, 1771]], 1600],
   [1171, 2, 1600]),
  ('regression variant: split gap threshold 2', [[[1351, 1591], [1021, 1261], [600, 840], [900, 960]], 1701],
   [991, 2, 1701]),
  ('partial repair guard 3', [[[720, 900], [490, 500], [480, 660]], 1655], [420, 0, 0]),
  ('boundary control 4', [[[600, 900], [610, 620], [1000, 1100]], 1655], [500, 1, 827]),
  ('boundary control 5', [[[480, 720], [780, 1080]], 1600], [600, 0, 0]),
  ('normal control 6', [[[600, 840]], 1520], [240, 0, 0]),
  ('normal control 7', [[[360, 420], [420, 540]], 1520], [180, 0, 0]),
  ('normal control 8', [[[1081, 1321], [720, 1020], [360, 600]], 1655], [961, 2, 1655])]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: split gap threshold 1[600, 1, 800][600, 0, 0]Failed
regression variant: split gap threshold 2[990, 2, 1701][990, 1, 1701]Failed
partial repair guard 3[810, 3, 1655][810, 2, 1655]Failed
boundary control 4[600, 0, 0][600, 0, 0]Passed
boundary control 5[590, 4, 1654][590, 4, 1654]Passed
normal control 6[690, 1, 1600][690, 1, 1600]Passed
normal control 7[330, 0, 0][330, 0, 0]Passed
normal control 8[781, 3, 1701][781, 3, 1701]Passed

SHA-256 / 76ba0199e3a495553bc54b9142c9ba430d601980b0d43aa5d1278df470049426

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(segments, min_wage):
    seg = sorted(segments)
    merged = []
    for s, e in seg:
        if merged and s <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], e)
        else:
            merged.append([s, e])
    spread = merged[-1][1] - merged[0][0]
    splits = sum(1 for a, b in zip(merged, merged[1:]) if b[0] - a[0] > 60)
    if spread > 600:
        premium = min_wage
    else:
        premium = (min_wage // 2) * min(splits, 2)
    return [spread, splits, premium]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: split gap threshold 1', [[[480, 720], [780, 1080]], 1600], [600, 0, 0]),
  ('regression variant: split gap threshold 2',
   [[[480, 540], [630, 870], [930, 1170], [1170, 1470], [490, 500]], 1701], [990, 1, 1701]),
  ('partial repair guard 3', [[[870, 1110], [1170, 1290], [480, 540], [720, 780]], 1655], [810, 2, 1655]),
  ('boundary control 4', [[[420, 1020]], 1600], [600, 0, 0]),
  ('boundary control 5', [[[420, 480], [550, 600], [700, 760], [840, 900], [980, 1010]], 1655],
   [590, 4, 1654]),
  ('normal control 6', [[[810, 870], [840, 1020], [600, 720], [990, 1290]], 1600], [690, 1, 1600]),
  ('normal control 7', [[[630, 690], [760, 770], [720, 780], [750, 810], [480, 660]], 1600], [330, 0, 0]),
  ('normal control 8', [[[601, 841], [480, 540], [1081, 1261], [931, 991]], 1701], [781, 3, 1701])],
 [('regression: split gap threshold 1', [[[420, 600], [660, 780], [840, 1020]], 1701], [600, 0, 0]),
  ('regression variant: split gap threshold 2', [[[899, 959], [420, 720], [779, 839]], 1701], [539, 0, 0]),
  ('partial repair guard 3', [[[610, 620], [360, 540], [600, 900]], 1655], [540, 0, 0]),
  ('boundary control 4', [[[900, 1000], [420, 600], [700, 800]], 1520], [580, 2, 1520]),
  ('boundary control 5', [[[480, 720], [781, 1080]], 1600], [600, 1, 800]),
  ('normal control 6', [[[450, 510], [360, 420], [370, 380]], 1520], [150, 0, 0]),
  ('normal control 7', [[[960, 1140], [420, 480], [480, 720]], 1520], [720, 1, 1520]),
  ('normal control 8', [[[420, 600], [659, 719], [749, 869]], 1655], [449, 0, 0])],
 [('regression: split gap threshold 1', [[[600, 780], [1139, 1379], [839, 1079]], 1701], [779, 0, 1701]),
  ('regression variant: split gap threshold 2', [[[1080, 1140], [420, 600], [660, 960], [1140, 1320]], 1600],
   [900, 1, 1600]),
  ('partial repair guard 3', [[[420, 480], [570, 870], [430, 440], [929, 1229]], 1655], [809, 1, 1655]),
  ('boundary control 4', [[[600, 630], [700, 730], [800, 830], [900, 930]], 1701], [330, 3, 1700]),
  ('boundary control 5', [[[420, 1021]], 1600], [601, 0, 1600]),
  ('normal control 6', [[[570, 810], [900, 1200], [910, 920], [420, 540]], 1701], [780, 1, 1701]),
  ('normal control 7', [[[420, 600], [840, 1080]], 1600], [660, 1, 1600]),
  ('normal control 8', [[[690, 750], [480, 720]], 1701], [270, 0, 0])],
 [('regression: split gap threshold 1', [[[1079, 1319], [1378, 1618], [720, 1020], [600, 660]], 1701],
   [1018, 0, 1701]),
  ('regression variant: split gap threshold 2', [[[1021, 1261], [781, 961], [480, 720]], 1600],
   [781, 1, 1600]),
  ('partial repair guard 3', [[[570, 810], [900, 1200], [910, 920], [420, 540]], 1701], [780, 1, 1701]),
  ('boundary control 4', [[[420, 600], [660, 780], [840, 1020]], 1701], [600, 0, 0]),
  ('boundary control 5', [[[420, 450], [520, 550], [620, 650], [720, 750]], 1600], [330, 3, 1600]),
  ('normal control 6', [[[750, 810], [900, 1140], [1110, 1230], [480, 780]], 1600], [750, 1, 1600]),
  ('normal control 7', [[[430, 440], [420, 720]], 1655], [300, 0, 0]),
  ('normal control 8', [[[480, 540], [630, 870], [930, 1170], [1170, 1470], [490, 500]], 1701],
   [990, 1, 1701])],
 [('regression: split gap threshold 1', [[[1110, 1410], [600, 900], [990, 1050], [1471, 1771]], 1600],
   [1171, 2, 1600]),
  ('regression variant: split gap threshold 2', [[[1351, 1591], [1021, 1261], [600, 840], [900, 960]], 1701],
   [991, 2, 1701]),
  ('partial repair guard 3', [[[720, 900], [490, 500], [480, 660]], 1655], [420, 0, 0]),
  ('boundary control 4', [[[600, 900], [610, 620], [1000, 1100]], 1655], [500, 1, 827]),
  ('boundary control 5', [[[480, 720], [780, 1080]], 1600], [600, 0, 0]),
  ('normal control 6', [[[600, 840]], 1520], [240, 0, 0]),
  ('normal control 7', [[[360, 420], [420, 540]], 1520], [180, 0, 0]),
  ('normal control 8', [[[1081, 1321], [720, 1020], [360, 600]], 1655], [961, 2, 1655])]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: split gap threshold 1[600, 1, 800][600, 0, 0]Failed
regression variant: split gap threshold 2[990, 2, 1701][990, 1, 1701]Failed
partial repair guard 3[810, 3, 1655][810, 2, 1655]Failed
boundary control 4[600, 0, 0][600, 0, 0]Passed
boundary control 5[590, 4, 1654][590, 4, 1654]Passed
normal control 6[690, 1, 1600][690, 1, 1600]Passed
normal control 7[330, 1, 800][330, 0, 0]Failed
normal control 8[781, 3, 1701][781, 3, 1701]Passed

SHA-256 / e327a47503309b377c901eebac688e820ff7767372c652b1a3060cac9df2ad8c

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json

N = 1
observations = []
def solve(segments, min_wage):
    seg = sorted(segments)
    merged = []
    for s, e in seg:
        if merged and s <= merged[-1][1]:
            merged[-1][1] = max(merged[-1][1], e)
        else:
            merged.append([s, e])
    spread = merged[-1][1] - merged[0][0]
    splits = sum(1 for a, b in zip(merged, merged[1:]) if b[0] - a[1] > 60)
    if spread > 600:
        premium = min_wage
    else:
        premium = (min_wage // 2) * min(splits, 2)
    return [spread, splits, premium]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: split gap threshold 1', [[[480, 720], [780, 1080]], 1600], [600, 0, 0]),
  ('regression variant: split gap threshold 2',
   [[[480, 540], [630, 870], [930, 1170], [1170, 1470], [490, 500]], 1701], [990, 1, 1701]),
  ('partial repair guard 3', [[[870, 1110], [1170, 1290], [480, 540], [720, 780]], 1655], [810, 2, 1655]),
  ('boundary control 4', [[[420, 1020]], 1600], [600, 0, 0]),
  ('boundary control 5', [[[420, 480], [550, 600], [700, 760], [840, 900], [980, 1010]], 1655],
   [590, 4, 1654]),
  ('normal control 6', [[[810, 870], [840, 1020], [600, 720], [990, 1290]], 1600], [690, 1, 1600]),
  ('normal control 7', [[[630, 690], [760, 770], [720, 780], [750, 810], [480, 660]], 1600], [330, 0, 0]),
  ('normal control 8', [[[601, 841], [480, 540], [1081, 1261], [931, 991]], 1701], [781, 3, 1701])],
 [('regression: split gap threshold 1', [[[420, 600], [660, 780], [840, 1020]], 1701], [600, 0, 0]),
  ('regression variant: split gap threshold 2', [[[899, 959], [420, 720], [779, 839]], 1701], [539, 0, 0]),
  ('partial repair guard 3', [[[610, 620], [360, 540], [600, 900]], 1655], [540, 0, 0]),
  ('boundary control 4', [[[900, 1000], [420, 600], [700, 800]], 1520], [580, 2, 1520]),
  ('boundary control 5', [[[480, 720], [781, 1080]], 1600], [600, 1, 800]),
  ('normal control 6', [[[450, 510], [360, 420], [370, 380]], 1520], [150, 0, 0]),
  ('normal control 7', [[[960, 1140], [420, 480], [480, 720]], 1520], [720, 1, 1520]),
  ('normal control 8', [[[420, 600], [659, 719], [749, 869]], 1655], [449, 0, 0])],
 [('regression: split gap threshold 1', [[[600, 780], [1139, 1379], [839, 1079]], 1701], [779, 0, 1701]),
  ('regression variant: split gap threshold 2', [[[1080, 1140], [420, 600], [660, 960], [1140, 1320]], 1600],
   [900, 1, 1600]),
  ('partial repair guard 3', [[[420, 480], [570, 870], [430, 440], [929, 1229]], 1655], [809, 1, 1655]),
  ('boundary control 4', [[[600, 630], [700, 730], [800, 830], [900, 930]], 1701], [330, 3, 1700]),
  ('boundary control 5', [[[420, 1021]], 1600], [601, 0, 1600]),
  ('normal control 6', [[[570, 810], [900, 1200], [910, 920], [420, 540]], 1701], [780, 1, 1701]),
  ('normal control 7', [[[420, 600], [840, 1080]], 1600], [660, 1, 1600]),
  ('normal control 8', [[[690, 750], [480, 720]], 1701], [270, 0, 0])],
 [('regression: split gap threshold 1', [[[1079, 1319], [1378, 1618], [720, 1020], [600, 660]], 1701],
   [1018, 0, 1701]),
  ('regression variant: split gap threshold 2', [[[1021, 1261], [781, 961], [480, 720]], 1600],
   [781, 1, 1600]),
  ('partial repair guard 3', [[[570, 810], [900, 1200], [910, 920], [420, 540]], 1701], [780, 1, 1701]),
  ('boundary control 4', [[[420, 600], [660, 780], [840, 1020]], 1701], [600, 0, 0]),
  ('boundary control 5', [[[420, 450], [520, 550], [620, 650], [720, 750]], 1600], [330, 3, 1600]),
  ('normal control 6', [[[750, 810], [900, 1140], [1110, 1230], [480, 780]], 1600], [750, 1, 1600]),
  ('normal control 7', [[[430, 440], [420, 720]], 1655], [300, 0, 0]),
  ('normal control 8', [[[480, 540], [630, 870], [930, 1170], [1170, 1470], [490, 500]], 1701],
   [990, 1, 1701])],
 [('regression: split gap threshold 1', [[[1110, 1410], [600, 900], [990, 1050], [1471, 1771]], 1600],
   [1171, 2, 1600]),
  ('regression variant: split gap threshold 2', [[[1351, 1591], [1021, 1261], [600, 840], [900, 960]], 1701],
   [991, 2, 1701]),
  ('partial repair guard 3', [[[720, 900], [490, 500], [480, 660]], 1655], [420, 0, 0]),
  ('boundary control 4', [[[600, 900], [610, 620], [1000, 1100]], 1655], [500, 1, 827]),
  ('boundary control 5', [[[480, 720], [780, 1080]], 1600], [600, 0, 0]),
  ('normal control 6', [[[600, 840]], 1520], [240, 0, 0]),
  ('normal control 7', [[[360, 420], [420, 540]], 1520], [180, 0, 0]),
  ('normal control 8', [[[1081, 1321], [720, 1020], [360, 600]], 1655], [961, 2, 1655])]]
for label, args, expected in fixtures[N-1]:
    check(label, solve(*args), expected)
print(json.dumps({"observations": observations, "passed": all(x["passed"] for x in observations)}, ensure_ascii=False))
raise SystemExit(0 if all(x["passed"] for x in observations) else 1)
Boundary fixtureActualExpectedOutcome
regression: split gap threshold 1[600, 0, 0][600, 0, 0]Passed
regression variant: split gap threshold 2[990, 1, 1701][990, 1, 1701]Passed
partial repair guard 3[810, 2, 1655][810, 2, 1655]Passed
boundary control 4[600, 0, 0][600, 0, 0]Passed
boundary control 5[590, 4, 1654][590, 4, 1654]Passed
normal control 6[690, 1, 1600][690, 1, 1600]Passed
normal control 7[330, 0, 0][330, 0, 0]Passed
normal control 8[781, 3, 1701][781, 3, 1701]Passed

SHA-256 / ddc1366f7e2f0bd51a0353ec710d5240913e3667debc0e294a349410e536c301

Verification & scope

Stipulated toy labor rule for a bounded roster model; it is not legal advice and does not claim conformance with any jurisdiction, award, or collective agreement. This reproducer isolates one failure mechanism. Results cover the supplied fixtures. Variants within a family share a test contract and should remain grouped when constructing evaluation splits. Related mechanisms with a shared evaluation_group must also remain together; these controlled models are not independent production incidents.

Observations recorded using Python 3.12.14 at 2026-09-29T14:51:59.449876+00:00.

Case digest / 20ee40cdb0bc70c12e384d341fb4c31c92d74e9d0a1cbc77428d3577ac68493c