FAILURE MAP
← Case archive

FA-93921 / Shift rostering labor rules / Open access

Spread premium stacked with split premium · case 01

Long split days receive both premiums although the spread premium replaces the split premium.

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

ROOT CAUSE

The spread branch adds the split premium instead of replacing it.

VERIFIED REPAIR

When spread exceeds ten hours pay exactly one hour at minimum wage.

Unsuccessful approach: Doubling the spread premium for any split still stacks the two premiums.

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 + (min_wage // 2) * min(splits, 2)
    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: premium precedence 1', [[[610, 620], [1259, 1319], [900, 1200], [600, 720]], 1520],
   [719, 1, 1520]),
  ('regression variant: premium precedence 2', [[[420, 480], [570, 870], [430, 440], [929, 1229]], 1655],
   [809, 1, 1655]),
  ('partial repair guard 3', [[[961, 1081], [1171, 1231], [971, 981], [600, 900]], 1655], [631, 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', [[[660, 960], [670, 680], [600, 660]], 1701], [360, 0, 0]),
  ('normal control 7', [[[420, 540], [430, 440]], 1600], [120, 0, 0]),
  ('normal control 8', [[[600, 840]], 1520], [240, 0, 0])],
 [('regression: premium precedence 1',
   [[[1261, 1441], [430, 440], [780, 1020], [420, 660], [1081, 1261]], 1600], [1021, 2, 1600]),
  ('regression variant: premium precedence 2', [[[900, 1140], [360, 420], [600, 780]], 1600], [780, 2, 1600]),
  ('partial repair guard 3', [[[1291, 1471], [931, 1111], [750, 870], [480, 660], [490, 500]], 1701],
   [991, 3, 1701]),
  ('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', [[[1021, 1261], [781, 961], [480, 720]], 1600], [781, 1, 1600]),
  ('normal control 7', [[[600, 660]], 1520], [60, 0, 0]),
  ('normal control 8', [[[570, 690], [360, 480]], 1600], [330, 1, 800])],
 [('regression: premium precedence 1', [[[1110, 1410], [600, 900], [990, 1050], [1471, 1771]], 1600],
   [1171, 2, 1600]),
  ('regression variant: premium precedence 2', [[[1320, 1560], [960, 1080], [480, 720], [970, 980]], 1600],
   [1080, 2, 1600]),
  ('partial repair guard 3', [[[570, 810], [900, 1200], [910, 920], [420, 540]], 1701], [780, 1, 1701]),
  ('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', [[[820, 830], [810, 990], [600, 720]], 1520], [390, 1, 760]),
  ('normal control 7', [[[480, 540]], 1600], [60, 0, 0]),
  ('normal control 8', [[[780, 1020], [1380, 1440], [1200, 1380], [420, 720], [1210, 1220]], 1655],
   [1020, 1, 1655])],
 [('regression: premium precedence 1', [[[870, 1110], [1170, 1290], [480, 540], [720, 780]], 1655],
   [810, 2, 1655]),
  ('regression variant: premium precedence 2', [[[780, 900], [990, 1290], [360, 540], [510, 810]], 1520],
   [930, 1, 1520]),
  ('partial repair guard 3', [[[420, 600], [661, 781], [1021, 1261]], 1701], [841, 2, 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', [[[600, 720]], 1600], [120, 0, 0]),
  ('normal control 7', [[[840, 900], [570, 750], [420, 540]], 1600], [480, 1, 800]),
  ('normal control 8', [[[720, 900], [420, 720], [961, 1141]], 1520], [721, 1, 1520])],
 [('regression: premium precedence 1',
   [[[600, 780], [931, 1171], [1291, 1351], [750, 870], [610, 620]], 1655], [751, 2, 1655]),
  ('regression variant: premium precedence 2', [[[1080, 1140], [420, 600], [660, 960], [1140, 1320]], 1600],
   [900, 1, 1600]),
  ('partial repair guard 3', [[[1440, 1500], [1080, 1200], [870, 1110], [480, 780]], 1655], [1020, 2, 1655]),
  ('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', [[[420, 660], [430, 440]], 1520], [240, 0, 0]),
  ('normal control 8', [[[600, 900], [610, 620]], 1701], [300, 0, 0])]]
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: premium precedence 1[719, 1, 2280][719, 1, 1520]Failed
regression variant: premium precedence 2[809, 1, 2482][809, 1, 1655]Failed
partial repair guard 3[631, 2, 3309][631, 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[360, 0, 0][360, 0, 0]Passed
normal control 7[120, 0, 0][120, 0, 0]Passed
normal control 8[240, 0, 0][240, 0, 0]Passed

SHA-256 / 5b2bc15f80b6a43ba0051960795e4b1833cba359a7b86e7830f18130c4b6f9bf

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[1] > 60)
    if spread > 600:
        premium = min_wage * (1 + (splits > 0))
    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: premium precedence 1', [[[610, 620], [1259, 1319], [900, 1200], [600, 720]], 1520],
   [719, 1, 1520]),
  ('regression variant: premium precedence 2', [[[420, 480], [570, 870], [430, 440], [929, 1229]], 1655],
   [809, 1, 1655]),
  ('partial repair guard 3', [[[961, 1081], [1171, 1231], [971, 981], [600, 900]], 1655], [631, 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', [[[660, 960], [670, 680], [600, 660]], 1701], [360, 0, 0]),
  ('normal control 7', [[[420, 540], [430, 440]], 1600], [120, 0, 0]),
  ('normal control 8', [[[600, 840]], 1520], [240, 0, 0])],
 [('regression: premium precedence 1',
   [[[1261, 1441], [430, 440], [780, 1020], [420, 660], [1081, 1261]], 1600], [1021, 2, 1600]),
  ('regression variant: premium precedence 2', [[[900, 1140], [360, 420], [600, 780]], 1600], [780, 2, 1600]),
  ('partial repair guard 3', [[[1291, 1471], [931, 1111], [750, 870], [480, 660], [490, 500]], 1701],
   [991, 3, 1701]),
  ('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', [[[1021, 1261], [781, 961], [480, 720]], 1600], [781, 1, 1600]),
  ('normal control 7', [[[600, 660]], 1520], [60, 0, 0]),
  ('normal control 8', [[[570, 690], [360, 480]], 1600], [330, 1, 800])],
 [('regression: premium precedence 1', [[[1110, 1410], [600, 900], [990, 1050], [1471, 1771]], 1600],
   [1171, 2, 1600]),
  ('regression variant: premium precedence 2', [[[1320, 1560], [960, 1080], [480, 720], [970, 980]], 1600],
   [1080, 2, 1600]),
  ('partial repair guard 3', [[[570, 810], [900, 1200], [910, 920], [420, 540]], 1701], [780, 1, 1701]),
  ('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', [[[820, 830], [810, 990], [600, 720]], 1520], [390, 1, 760]),
  ('normal control 7', [[[480, 540]], 1600], [60, 0, 0]),
  ('normal control 8', [[[780, 1020], [1380, 1440], [1200, 1380], [420, 720], [1210, 1220]], 1655],
   [1020, 1, 1655])],
 [('regression: premium precedence 1', [[[870, 1110], [1170, 1290], [480, 540], [720, 780]], 1655],
   [810, 2, 1655]),
  ('regression variant: premium precedence 2', [[[780, 900], [990, 1290], [360, 540], [510, 810]], 1520],
   [930, 1, 1520]),
  ('partial repair guard 3', [[[420, 600], [661, 781], [1021, 1261]], 1701], [841, 2, 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', [[[600, 720]], 1600], [120, 0, 0]),
  ('normal control 7', [[[840, 900], [570, 750], [420, 540]], 1600], [480, 1, 800]),
  ('normal control 8', [[[720, 900], [420, 720], [961, 1141]], 1520], [721, 1, 1520])],
 [('regression: premium precedence 1',
   [[[600, 780], [931, 1171], [1291, 1351], [750, 870], [610, 620]], 1655], [751, 2, 1655]),
  ('regression variant: premium precedence 2', [[[1080, 1140], [420, 600], [660, 960], [1140, 1320]], 1600],
   [900, 1, 1600]),
  ('partial repair guard 3', [[[1440, 1500], [1080, 1200], [870, 1110], [480, 780]], 1655], [1020, 2, 1655]),
  ('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', [[[420, 660], [430, 440]], 1520], [240, 0, 0]),
  ('normal control 8', [[[600, 900], [610, 620]], 1701], [300, 0, 0])]]
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: premium precedence 1[719, 1, 3040][719, 1, 1520]Failed
regression variant: premium precedence 2[809, 1, 3310][809, 1, 1655]Failed
partial repair guard 3[631, 2, 3310][631, 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[360, 0, 0][360, 0, 0]Passed
normal control 7[120, 0, 0][120, 0, 0]Passed
normal control 8[240, 0, 0][240, 0, 0]Passed

SHA-256 / cc82dc9e163e89a2a7ee8e6c147762991c008c3fc6e684f2f8b6401222a008e4

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: premium precedence 1', [[[610, 620], [1259, 1319], [900, 1200], [600, 720]], 1520],
   [719, 1, 1520]),
  ('regression variant: premium precedence 2', [[[420, 480], [570, 870], [430, 440], [929, 1229]], 1655],
   [809, 1, 1655]),
  ('partial repair guard 3', [[[961, 1081], [1171, 1231], [971, 981], [600, 900]], 1655], [631, 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', [[[660, 960], [670, 680], [600, 660]], 1701], [360, 0, 0]),
  ('normal control 7', [[[420, 540], [430, 440]], 1600], [120, 0, 0]),
  ('normal control 8', [[[600, 840]], 1520], [240, 0, 0])],
 [('regression: premium precedence 1',
   [[[1261, 1441], [430, 440], [780, 1020], [420, 660], [1081, 1261]], 1600], [1021, 2, 1600]),
  ('regression variant: premium precedence 2', [[[900, 1140], [360, 420], [600, 780]], 1600], [780, 2, 1600]),
  ('partial repair guard 3', [[[1291, 1471], [931, 1111], [750, 870], [480, 660], [490, 500]], 1701],
   [991, 3, 1701]),
  ('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', [[[1021, 1261], [781, 961], [480, 720]], 1600], [781, 1, 1600]),
  ('normal control 7', [[[600, 660]], 1520], [60, 0, 0]),
  ('normal control 8', [[[570, 690], [360, 480]], 1600], [330, 1, 800])],
 [('regression: premium precedence 1', [[[1110, 1410], [600, 900], [990, 1050], [1471, 1771]], 1600],
   [1171, 2, 1600]),
  ('regression variant: premium precedence 2', [[[1320, 1560], [960, 1080], [480, 720], [970, 980]], 1600],
   [1080, 2, 1600]),
  ('partial repair guard 3', [[[570, 810], [900, 1200], [910, 920], [420, 540]], 1701], [780, 1, 1701]),
  ('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', [[[820, 830], [810, 990], [600, 720]], 1520], [390, 1, 760]),
  ('normal control 7', [[[480, 540]], 1600], [60, 0, 0]),
  ('normal control 8', [[[780, 1020], [1380, 1440], [1200, 1380], [420, 720], [1210, 1220]], 1655],
   [1020, 1, 1655])],
 [('regression: premium precedence 1', [[[870, 1110], [1170, 1290], [480, 540], [720, 780]], 1655],
   [810, 2, 1655]),
  ('regression variant: premium precedence 2', [[[780, 900], [990, 1290], [360, 540], [510, 810]], 1520],
   [930, 1, 1520]),
  ('partial repair guard 3', [[[420, 600], [661, 781], [1021, 1261]], 1701], [841, 2, 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', [[[600, 720]], 1600], [120, 0, 0]),
  ('normal control 7', [[[840, 900], [570, 750], [420, 540]], 1600], [480, 1, 800]),
  ('normal control 8', [[[720, 900], [420, 720], [961, 1141]], 1520], [721, 1, 1520])],
 [('regression: premium precedence 1',
   [[[600, 780], [931, 1171], [1291, 1351], [750, 870], [610, 620]], 1655], [751, 2, 1655]),
  ('regression variant: premium precedence 2', [[[1080, 1140], [420, 600], [660, 960], [1140, 1320]], 1600],
   [900, 1, 1600]),
  ('partial repair guard 3', [[[1440, 1500], [1080, 1200], [870, 1110], [480, 780]], 1655], [1020, 2, 1655]),
  ('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', [[[420, 660], [430, 440]], 1520], [240, 0, 0]),
  ('normal control 8', [[[600, 900], [610, 620]], 1701], [300, 0, 0])]]
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: premium precedence 1[719, 1, 1520][719, 1, 1520]Passed
regression variant: premium precedence 2[809, 1, 1655][809, 1, 1655]Passed
partial repair guard 3[631, 2, 1655][631, 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[360, 0, 0][360, 0, 0]Passed
normal control 7[120, 0, 0][120, 0, 0]Passed
normal control 8[240, 0, 0][240, 0, 0]Passed

SHA-256 / 427ad6741a259b42e1a77dc9924d9da6f0af50b9a2b3ee217697f207c450851b

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

Case digest / dadbee397ad8ae91867789166db4bbc48f6b3f99e9d55d467b4af539909563ba