FAILURE MAP
← Case archive

FA-93896 / Shift rostering labor rules / Open access

Spread of hours computed from worked minutes instead of first-to-last span · case 01

Split days with long unpaid gaps never reach the spread premium.

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

ROOT CAUSE

Spread sums the worked segment lengths rather than spanning first start to last end.

VERIFIED REPAIR

Spread is the last merged end minus the first merged start.

Unsuccessful approach: Measuring from the end of the first segment drops the first segment from the span.

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 = sum(e - s for s, e in merged)
    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: spread measured as worked time 1', [[[480, 720], [780, 1080]], 1600], [600, 0, 0]),
  ('regression variant: spread measured as worked time 2',
   [[[610, 620], [1259, 1319], [900, 1200], [600, 720]], 1520], [719, 1, 1520]),
  ('partial repair guard 3', [[[900, 1000], [420, 600], [700, 800]], 1520], [580, 2, 1520]),
  ('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', [[[420, 720], [840, 900], [1201, 1321], [1080, 1140]], 1520], [901, 3, 1520]),
  ('normal control 7', [[[570, 750], [420, 540], [580, 590]], 1701], [330, 0, 0]),
  ('normal control 8', [[[1081, 1261], [420, 720], [781, 901], [430, 440], [1381, 1501]], 1520],
   [1081, 3, 1520])],
 [('regression: spread measured as worked time 1', [[[480, 720], [781, 1080]], 1600], [600, 1, 800]),
  ('regression variant: spread measured as worked time 2',
   [[[600, 840], [1501, 1561], [900, 960], [1021, 1261]], 1600], [961, 2, 1600]),
  ('partial repair guard 3', [[[1079, 1319], [1378, 1618], [720, 1020], [600, 660]], 1701], [1018, 0, 1701]),
  ('boundary control 4', [[[900, 1000], [420, 600], [700, 800]], 1520], [580, 2, 1520]),
  ('normal control 5', [[[1380, 1680], [600, 840], [1090, 1100], [1080, 1140], [870, 1050]], 1655],
   [1080, 1, 1655]),
  ('normal control 6', [[[420, 540], [510, 810], [1050, 1170]], 1655], [750, 1, 1655]),
  ('normal control 7', [[[910, 920], [600, 660], [1380, 1680], [900, 1200]], 1520], [1080, 2, 1520]),
  ('normal control 8', [[[420, 480]], 1520], [60, 0, 0])],
 [('regression: spread measured as worked time 1', [[[420, 600], [660, 780], [840, 1020]], 1701],
   [600, 0, 0]),
  ('regression variant: spread measured as worked time 2', [[[841, 1141], [1141, 1441], [600, 780]], 1600],
   [841, 1, 1600]),
  ('partial repair guard 3', [[[600, 780]], 1520], [180, 0, 0]),
  ('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', [[[600, 780], [900, 1080], [1200, 1440]], 1600], [840, 2, 1600]),
  ('normal control 7', [[[660, 960], [370, 380], [360, 600]], 1600], [600, 0, 0]),
  ('normal control 8', [[[630, 750], [640, 650], [420, 540]], 1701], [330, 1, 850])],
 [('regression: spread measured as worked time 1', [[[600, 900], [610, 620], [1000, 1100]], 1655],
   [500, 1, 827]),
  ('regression variant: spread measured as worked time 2',
   [[[1001, 1011], [480, 660], [630, 930], [991, 1051]], 1600], [571, 1, 800]),
  ('partial repair guard 3', [[[600, 840], [1501, 1561], [900, 960], [1021, 1261]], 1600], [961, 2, 1600]),
  ('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', [[[600, 660]], 1600], [60, 0, 0]),
  ('normal control 7', [[[1680, 1860], [960, 1200], [1260, 1560], [600, 720]], 1600], [1260, 2, 1600]),
  ('normal control 8', [[[420, 720], [1019, 1139], [1139, 1319], [720, 960]], 1600], [899, 0, 1600])],
 [('regression: spread measured as worked time 1', [[[900, 1000], [420, 600], [700, 800]], 1520],
   [580, 2, 1520]),
  ('regression variant: spread measured as worked time 2', [[[900, 1140], [360, 420], [600, 780]], 1600],
   [780, 2, 1600]),
  ('partial repair guard 3', [[[610, 620], [360, 540], [600, 900]], 1655], [540, 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], [1501, 1561], [900, 960], [1021, 1261]], 1600], [961, 2, 1600]),
  ('normal control 7', [[[480, 660]], 1600], [180, 0, 0]),
  ('normal control 8', [[[630, 930], [480, 540]], 1655], [450, 1, 827])]]
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: spread measured as worked time 1[540, 0, 0][600, 0, 0]Failed
regression variant: spread measured as worked time 2[480, 1, 760][719, 1, 1520]Failed
partial repair guard 3[380, 2, 1520][580, 2, 1520]Failed
boundary control 4[600, 0, 0][600, 0, 0]Passed
boundary control 5[260, 4, 1654][590, 4, 1654]Failed
normal control 6[540, 3, 1520][901, 3, 1520]Failed
normal control 7[300, 0, 0][330, 0, 0]Failed
normal control 8[720, 3, 1520][1081, 3, 1520]Failed

SHA-256 / b66862ee3ef0908cea1f8f11b9ca507c45853317c10b0943316c0aea9871af3b

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][1]
    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: spread measured as worked time 1', [[[480, 720], [780, 1080]], 1600], [600, 0, 0]),
  ('regression variant: spread measured as worked time 2',
   [[[610, 620], [1259, 1319], [900, 1200], [600, 720]], 1520], [719, 1, 1520]),
  ('partial repair guard 3', [[[900, 1000], [420, 600], [700, 800]], 1520], [580, 2, 1520]),
  ('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', [[[420, 720], [840, 900], [1201, 1321], [1080, 1140]], 1520], [901, 3, 1520]),
  ('normal control 7', [[[570, 750], [420, 540], [580, 590]], 1701], [330, 0, 0]),
  ('normal control 8', [[[1081, 1261], [420, 720], [781, 901], [430, 440], [1381, 1501]], 1520],
   [1081, 3, 1520])],
 [('regression: spread measured as worked time 1', [[[480, 720], [781, 1080]], 1600], [600, 1, 800]),
  ('regression variant: spread measured as worked time 2',
   [[[600, 840], [1501, 1561], [900, 960], [1021, 1261]], 1600], [961, 2, 1600]),
  ('partial repair guard 3', [[[1079, 1319], [1378, 1618], [720, 1020], [600, 660]], 1701], [1018, 0, 1701]),
  ('boundary control 4', [[[900, 1000], [420, 600], [700, 800]], 1520], [580, 2, 1520]),
  ('normal control 5', [[[1380, 1680], [600, 840], [1090, 1100], [1080, 1140], [870, 1050]], 1655],
   [1080, 1, 1655]),
  ('normal control 6', [[[420, 540], [510, 810], [1050, 1170]], 1655], [750, 1, 1655]),
  ('normal control 7', [[[910, 920], [600, 660], [1380, 1680], [900, 1200]], 1520], [1080, 2, 1520]),
  ('normal control 8', [[[420, 480]], 1520], [60, 0, 0])],
 [('regression: spread measured as worked time 1', [[[420, 600], [660, 780], [840, 1020]], 1701],
   [600, 0, 0]),
  ('regression variant: spread measured as worked time 2', [[[841, 1141], [1141, 1441], [600, 780]], 1600],
   [841, 1, 1600]),
  ('partial repair guard 3', [[[600, 780]], 1520], [180, 0, 0]),
  ('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', [[[600, 780], [900, 1080], [1200, 1440]], 1600], [840, 2, 1600]),
  ('normal control 7', [[[660, 960], [370, 380], [360, 600]], 1600], [600, 0, 0]),
  ('normal control 8', [[[630, 750], [640, 650], [420, 540]], 1701], [330, 1, 850])],
 [('regression: spread measured as worked time 1', [[[600, 900], [610, 620], [1000, 1100]], 1655],
   [500, 1, 827]),
  ('regression variant: spread measured as worked time 2',
   [[[1001, 1011], [480, 660], [630, 930], [991, 1051]], 1600], [571, 1, 800]),
  ('partial repair guard 3', [[[600, 840], [1501, 1561], [900, 960], [1021, 1261]], 1600], [961, 2, 1600]),
  ('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', [[[600, 660]], 1600], [60, 0, 0]),
  ('normal control 7', [[[1680, 1860], [960, 1200], [1260, 1560], [600, 720]], 1600], [1260, 2, 1600]),
  ('normal control 8', [[[420, 720], [1019, 1139], [1139, 1319], [720, 960]], 1600], [899, 0, 1600])],
 [('regression: spread measured as worked time 1', [[[900, 1000], [420, 600], [700, 800]], 1520],
   [580, 2, 1520]),
  ('regression variant: spread measured as worked time 2', [[[900, 1140], [360, 420], [600, 780]], 1600],
   [780, 2, 1600]),
  ('partial repair guard 3', [[[610, 620], [360, 540], [600, 900]], 1655], [540, 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], [1501, 1561], [900, 960], [1021, 1261]], 1600], [961, 2, 1600]),
  ('normal control 7', [[[480, 660]], 1600], [180, 0, 0]),
  ('normal control 8', [[[630, 930], [480, 540]], 1655], [450, 1, 827])]]
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: spread measured as worked time 1[360, 0, 0][600, 0, 0]Failed
regression variant: spread measured as worked time 2[599, 1, 760][719, 1, 1520]Failed
partial repair guard 3[400, 2, 1520][580, 2, 1520]Failed
boundary control 4[0, 0, 0][600, 0, 0]Failed
boundary control 5[530, 4, 1654][590, 4, 1654]Failed
normal control 6[601, 3, 1520][901, 3, 1520]Failed
normal control 7[210, 0, 0][330, 0, 0]Failed
normal control 8[781, 3, 1520][1081, 3, 1520]Failed

SHA-256 / 7395d6769549913b4b87a877a39914f12ded424bfe60ce9d0acfbb123e442823

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: spread measured as worked time 1', [[[480, 720], [780, 1080]], 1600], [600, 0, 0]),
  ('regression variant: spread measured as worked time 2',
   [[[610, 620], [1259, 1319], [900, 1200], [600, 720]], 1520], [719, 1, 1520]),
  ('partial repair guard 3', [[[900, 1000], [420, 600], [700, 800]], 1520], [580, 2, 1520]),
  ('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', [[[420, 720], [840, 900], [1201, 1321], [1080, 1140]], 1520], [901, 3, 1520]),
  ('normal control 7', [[[570, 750], [420, 540], [580, 590]], 1701], [330, 0, 0]),
  ('normal control 8', [[[1081, 1261], [420, 720], [781, 901], [430, 440], [1381, 1501]], 1520],
   [1081, 3, 1520])],
 [('regression: spread measured as worked time 1', [[[480, 720], [781, 1080]], 1600], [600, 1, 800]),
  ('regression variant: spread measured as worked time 2',
   [[[600, 840], [1501, 1561], [900, 960], [1021, 1261]], 1600], [961, 2, 1600]),
  ('partial repair guard 3', [[[1079, 1319], [1378, 1618], [720, 1020], [600, 660]], 1701], [1018, 0, 1701]),
  ('boundary control 4', [[[900, 1000], [420, 600], [700, 800]], 1520], [580, 2, 1520]),
  ('normal control 5', [[[1380, 1680], [600, 840], [1090, 1100], [1080, 1140], [870, 1050]], 1655],
   [1080, 1, 1655]),
  ('normal control 6', [[[420, 540], [510, 810], [1050, 1170]], 1655], [750, 1, 1655]),
  ('normal control 7', [[[910, 920], [600, 660], [1380, 1680], [900, 1200]], 1520], [1080, 2, 1520]),
  ('normal control 8', [[[420, 480]], 1520], [60, 0, 0])],
 [('regression: spread measured as worked time 1', [[[420, 600], [660, 780], [840, 1020]], 1701],
   [600, 0, 0]),
  ('regression variant: spread measured as worked time 2', [[[841, 1141], [1141, 1441], [600, 780]], 1600],
   [841, 1, 1600]),
  ('partial repair guard 3', [[[600, 780]], 1520], [180, 0, 0]),
  ('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', [[[600, 780], [900, 1080], [1200, 1440]], 1600], [840, 2, 1600]),
  ('normal control 7', [[[660, 960], [370, 380], [360, 600]], 1600], [600, 0, 0]),
  ('normal control 8', [[[630, 750], [640, 650], [420, 540]], 1701], [330, 1, 850])],
 [('regression: spread measured as worked time 1', [[[600, 900], [610, 620], [1000, 1100]], 1655],
   [500, 1, 827]),
  ('regression variant: spread measured as worked time 2',
   [[[1001, 1011], [480, 660], [630, 930], [991, 1051]], 1600], [571, 1, 800]),
  ('partial repair guard 3', [[[600, 840], [1501, 1561], [900, 960], [1021, 1261]], 1600], [961, 2, 1600]),
  ('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', [[[600, 660]], 1600], [60, 0, 0]),
  ('normal control 7', [[[1680, 1860], [960, 1200], [1260, 1560], [600, 720]], 1600], [1260, 2, 1600]),
  ('normal control 8', [[[420, 720], [1019, 1139], [1139, 1319], [720, 960]], 1600], [899, 0, 1600])],
 [('regression: spread measured as worked time 1', [[[900, 1000], [420, 600], [700, 800]], 1520],
   [580, 2, 1520]),
  ('regression variant: spread measured as worked time 2', [[[900, 1140], [360, 420], [600, 780]], 1600],
   [780, 2, 1600]),
  ('partial repair guard 3', [[[610, 620], [360, 540], [600, 900]], 1655], [540, 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], [1501, 1561], [900, 960], [1021, 1261]], 1600], [961, 2, 1600]),
  ('normal control 7', [[[480, 660]], 1600], [180, 0, 0]),
  ('normal control 8', [[[630, 930], [480, 540]], 1655], [450, 1, 827])]]
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: spread measured as worked time 1[600, 0, 0][600, 0, 0]Passed
regression variant: spread measured as worked time 2[719, 1, 1520][719, 1, 1520]Passed
partial repair guard 3[580, 2, 1520][580, 2, 1520]Passed
boundary control 4[600, 0, 0][600, 0, 0]Passed
boundary control 5[590, 4, 1654][590, 4, 1654]Passed
normal control 6[901, 3, 1520][901, 3, 1520]Passed
normal control 7[330, 0, 0][330, 0, 0]Passed
normal control 8[1081, 3, 1520][1081, 3, 1520]Passed

SHA-256 / 11025258de4db8d57dd3343421ee62b932d9c4e4d21e24824d5be8dd50c4b774

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.388573+00:00.

Case digest / 1786c5ca00e26255abb5c62741a45d7318478bb8d86ae48e220bf346092b68c1