FAILURE MAP
← Case archive

FA-93876 / Shift rostering labor rules / Open access

Double-time minutes also counted in the time-and-a-half band · case 01

Long days pay minutes past 12 hours at both 1.5x and 2x.

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

ROOT CAUSE

The 1.5x daily band is not capped at the 240 minutes between the 8- and 12-hour thresholds.

VERIFIED REPAIR

Cap the 1.5x daily band at 240 minutes.

Unsuccessful approach: Dropping the whole 1.5x band on days over 12 hours underpays hours 8 to 12.

Case contract

Seven daily worked minutes (Mon..Sun) and an hourly rate in cents. Daily: first 480 minutes regular, next 240 at 1.5x, beyond 720 at 2x. If all seven days are worked, day 7 pays its first 480 minutes at 1.5x and the rest at 2x. Regular minutes above 2400 in the week move to 1.5x (daily overtime minutes never count toward the weekly threshold). Pay is computed exactly and rounded half up to a cent once. Return [regular, ot15, ot2, pay_cents].

Why this case matters

Overtime classification mistakes are a classic payroll-roster defect: pyramiding, seventh-day rules and rounding stage all change what workers are paid.

1 / The failure

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

N = 1
observations = []
def solve(days, rate):
    reg = ot15 = ot2 = 0
    seventh = all(m > 0 for m in days)
    for d, m in enumerate(days):
        if d == 6 and seventh:
            ot15 += min(m, 480)
            ot2 += max(m - 480, 0)
            continue
        reg += min(m, 480)
        ot15 += max(m - 480, 0)
        ot2 += max(m - 720, 0)
    excess = max(reg - 2400, 0)
    reg -= excess
    ot15 += excess
    units = reg * rate * 2 + ot15 * rate * 3 + ot2 * rate * 4
    pay = (units + 60) // 120
    return [reg, ot15, ot2, pay]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: daily overtime band cap 1', [[900, 0, 0, 0, 0, 0, 0], 1725], [480, 240, 180, 34500]),
  ('regression variant: daily overtime band cap 2', [[480, 0, 480, 721, 720, 240, 720], 1810],
   [2400, 960, 1, 115900]),
  ('partial repair guard 3', [[900, 720, 721, 600, 201, 720, 0], 2250], [2400, 1281, 181, 175631]),
  ('boundary control 4', [[480, 480, 480, 480, 480, 480, 0], 2000], [2400, 480, 0, 104000]),
  ('boundary control 5', [[9, 0, 0, 0, 0, 0, 0], 30], [9, 0, 0, 5]),
  ('normal control 6', [[600, 510, 300, 300, 510, 480, 300], 1725], [2400, 600, 0, 94875]),
  ('normal control 7', [[900, 721, 0, 900, 481, 450, 780], 30], [2400, 1411, 421, 2679]),
  ('normal control 8', [[600, 450, 721, 600, 481, 720, 480], 1500], [2400, 1651, 1, 121963])],
 [('regression: daily overtime band cap 1', [[481, 119, 900, 256, 240, 20, 480], 2000],
   [1595, 721, 180, 101217]),
  ('regression variant: daily overtime band cap 2', [[0, 240, 0, 540, 780, 0, 0], 30], [1200, 300, 60, 885]),
  ('partial repair guard 3', [[450, 0, 908, 480, 721, 540, 540], 2250], [2400, 1050, 189, 163238]),
  ('boundary control 4', [[0, 480, 480, 480, 480, 480, 480], 1500], [2400, 480, 0, 78000]),
  ('boundary control 5', [[480, 480, 480, 480, 480, 480, 0], 2000], [2400, 480, 0, 104000]),
  ('normal control 6', [[780, 480, 0, 240, 0, 0, 481], 1500], [1680, 241, 60, 54038]),
  ('normal control 7', [[510, 510, 480, 600, 480, 600, 480], 2000], [2400, 1260, 0, 143000]),
  ('normal control 8', [[721, 150, 480, 450, 504, 540, 450], 30], [2400, 894, 1, 1872])],
 [('regression: daily overtime band cap 1', [[540, 450, 450, 900, 720, 240, 540], 2250],
   [2400, 1200, 240, 175500]),
  ('regression variant: daily overtime band cap 2', [[480, 721, 240, 481, 498, 450, 481], 1725],
   [2400, 949, 2, 110041]),
  ('partial repair guard 3', [[450, 780, 450, 480, 481, 481, 0], 1500], [2400, 662, 60, 87825]),
  ('boundary control 4', [[27, 0, 0, 0, 0, 0, 0], 30], [27, 0, 0, 14]),
  ('boundary control 5', [[0, 480, 480, 480, 480, 480, 480], 1500], [2400, 480, 0, 78000]),
  ('normal control 6', [[480, 480, 480, 480, 300, 480, 510], 90], [2400, 780, 30, 5445]),
  ('normal control 7', [[0, 37, 780, 0, 240, 240, 240], 30], [1237, 240, 60, 859]),
  ('normal control 8', [[900, 600, 900, 481, 737, 381, 780], 1500], [2400, 1702, 677, 157675])],
 [('regression: daily overtime band cap 1', [[0, 721, 240, 480, 900, 480, 0], 2250],
   [2160, 480, 181, 121575]),
  ('regression variant: daily overtime band cap 2', [[480, 600, 600, 720, 480, 780, 540], 2000],
   [2400, 1680, 120, 172000]),
  ('partial repair guard 3', [[335, 540, 0, 900, 0, 540, 240], 1725], [2015, 360, 180, 83806]),
  ('boundary control 4', [[600, 600, 600, 600, 600, 0, 0], 1500], [2400, 600, 0, 82500]),
  ('boundary control 5', [[27, 0, 0, 0, 0, 0, 0], 30], [27, 0, 0, 14]),
  ('normal control 6', [[721, 150, 480, 450, 504, 540, 450], 30], [2400, 894, 1, 1872]),
  ('normal control 7', [[510, 480, 300, 300, 600, 300, 510], 1725], [2340, 630, 30, 96169]),
  ('normal control 8', [[300, 600, 510, 600, 510, 600, 600], 1810], [2400, 1200, 120, 133940])],
 [('regression: daily overtime band cap 1', [[780, 780, 721, 450, 0, 0, 0], 2250], [1890, 720, 121, 120450]),
  ('regression variant: daily overtime band cap 2', [[480, 480, 481, 946, 720, 540, 450], 2000],
   [2400, 1471, 226, 168617]),
  ('partial repair guard 3', [[0, 14, 481, 481, 720, 720, 900], 2250], [2400, 736, 180, 144900]),
  ('boundary control 4', [[540, 540, 540, 540, 540, 540, 60], 2000], [2400, 900, 0, 125000]),
  ('boundary control 5', [[600, 600, 600, 600, 600, 0, 0], 1500], [2400, 600, 0, 82500]),
  ('normal control 6', [[300, 480, 600, 300, 510, 510, 510], 2000], [2400, 780, 30, 121000]),
  ('normal control 7', [[300, 480, 600, 300, 480, 480, 480], 2000], [2400, 720, 0, 116000]),
  ('normal control 8', [[510, 480, 300, 510, 300, 480, 480], 30], [2400, 660, 0, 1695])]]
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: daily overtime band cap 1[480, 420, 180, 42263][480, 240, 180, 34500]Failed
regression variant: daily overtime band cap 2[2400, 961, 1, 115946][2400, 960, 1, 115900]Failed
partial repair guard 3[2400, 1462, 181, 185813][2400, 1281, 181, 175631]Failed
boundary control 4[2400, 480, 0, 104000][2400, 480, 0, 104000]Passed
boundary control 5[9, 0, 0, 5][9, 0, 0, 5]Passed
normal control 6[2400, 600, 0, 94875][2400, 600, 0, 94875]Passed
normal control 7[2400, 1832, 421, 2995][2400, 1411, 421, 2679]Failed
normal control 8[2400, 1652, 1, 122000][2400, 1651, 1, 121963]Failed

SHA-256 / 1a4933728bd9cf7171185372208a142eb727f0452cf532c20ea3fe8222592f5b

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(days, rate):
    reg = ot15 = ot2 = 0
    seventh = all(m > 0 for m in days)
    for d, m in enumerate(days):
        if d == 6 and seventh:
            ot15 += min(m, 480)
            ot2 += max(m - 480, 0)
            continue
        reg += min(m, 480)
        ot15 += min(max(m - 480, 0), 240) if m <= 720 else 0
        ot2 += max(m - 720, 0)
    excess = max(reg - 2400, 0)
    reg -= excess
    ot15 += excess
    units = reg * rate * 2 + ot15 * rate * 3 + ot2 * rate * 4
    pay = (units + 60) // 120
    return [reg, ot15, ot2, pay]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: daily overtime band cap 1', [[900, 0, 0, 0, 0, 0, 0], 1725], [480, 240, 180, 34500]),
  ('regression variant: daily overtime band cap 2', [[480, 0, 480, 721, 720, 240, 720], 1810],
   [2400, 960, 1, 115900]),
  ('partial repair guard 3', [[900, 720, 721, 600, 201, 720, 0], 2250], [2400, 1281, 181, 175631]),
  ('boundary control 4', [[480, 480, 480, 480, 480, 480, 0], 2000], [2400, 480, 0, 104000]),
  ('boundary control 5', [[9, 0, 0, 0, 0, 0, 0], 30], [9, 0, 0, 5]),
  ('normal control 6', [[600, 510, 300, 300, 510, 480, 300], 1725], [2400, 600, 0, 94875]),
  ('normal control 7', [[900, 721, 0, 900, 481, 450, 780], 30], [2400, 1411, 421, 2679]),
  ('normal control 8', [[600, 450, 721, 600, 481, 720, 480], 1500], [2400, 1651, 1, 121963])],
 [('regression: daily overtime band cap 1', [[481, 119, 900, 256, 240, 20, 480], 2000],
   [1595, 721, 180, 101217]),
  ('regression variant: daily overtime band cap 2', [[0, 240, 0, 540, 780, 0, 0], 30], [1200, 300, 60, 885]),
  ('partial repair guard 3', [[450, 0, 908, 480, 721, 540, 540], 2250], [2400, 1050, 189, 163238]),
  ('boundary control 4', [[0, 480, 480, 480, 480, 480, 480], 1500], [2400, 480, 0, 78000]),
  ('boundary control 5', [[480, 480, 480, 480, 480, 480, 0], 2000], [2400, 480, 0, 104000]),
  ('normal control 6', [[780, 480, 0, 240, 0, 0, 481], 1500], [1680, 241, 60, 54038]),
  ('normal control 7', [[510, 510, 480, 600, 480, 600, 480], 2000], [2400, 1260, 0, 143000]),
  ('normal control 8', [[721, 150, 480, 450, 504, 540, 450], 30], [2400, 894, 1, 1872])],
 [('regression: daily overtime band cap 1', [[540, 450, 450, 900, 720, 240, 540], 2250],
   [2400, 1200, 240, 175500]),
  ('regression variant: daily overtime band cap 2', [[480, 721, 240, 481, 498, 450, 481], 1725],
   [2400, 949, 2, 110041]),
  ('partial repair guard 3', [[450, 780, 450, 480, 481, 481, 0], 1500], [2400, 662, 60, 87825]),
  ('boundary control 4', [[27, 0, 0, 0, 0, 0, 0], 30], [27, 0, 0, 14]),
  ('boundary control 5', [[0, 480, 480, 480, 480, 480, 480], 1500], [2400, 480, 0, 78000]),
  ('normal control 6', [[480, 480, 480, 480, 300, 480, 510], 90], [2400, 780, 30, 5445]),
  ('normal control 7', [[0, 37, 780, 0, 240, 240, 240], 30], [1237, 240, 60, 859]),
  ('normal control 8', [[900, 600, 900, 481, 737, 381, 780], 1500], [2400, 1702, 677, 157675])],
 [('regression: daily overtime band cap 1', [[0, 721, 240, 480, 900, 480, 0], 2250],
   [2160, 480, 181, 121575]),
  ('regression variant: daily overtime band cap 2', [[480, 600, 600, 720, 480, 780, 540], 2000],
   [2400, 1680, 120, 172000]),
  ('partial repair guard 3', [[335, 540, 0, 900, 0, 540, 240], 1725], [2015, 360, 180, 83806]),
  ('boundary control 4', [[600, 600, 600, 600, 600, 0, 0], 1500], [2400, 600, 0, 82500]),
  ('boundary control 5', [[27, 0, 0, 0, 0, 0, 0], 30], [27, 0, 0, 14]),
  ('normal control 6', [[721, 150, 480, 450, 504, 540, 450], 30], [2400, 894, 1, 1872]),
  ('normal control 7', [[510, 480, 300, 300, 600, 300, 510], 1725], [2340, 630, 30, 96169]),
  ('normal control 8', [[300, 600, 510, 600, 510, 600, 600], 1810], [2400, 1200, 120, 133940])],
 [('regression: daily overtime band cap 1', [[780, 780, 721, 450, 0, 0, 0], 2250], [1890, 720, 121, 120450]),
  ('regression variant: daily overtime band cap 2', [[480, 480, 481, 946, 720, 540, 450], 2000],
   [2400, 1471, 226, 168617]),
  ('partial repair guard 3', [[0, 14, 481, 481, 720, 720, 900], 2250], [2400, 736, 180, 144900]),
  ('boundary control 4', [[540, 540, 540, 540, 540, 540, 60], 2000], [2400, 900, 0, 125000]),
  ('boundary control 5', [[600, 600, 600, 600, 600, 0, 0], 1500], [2400, 600, 0, 82500]),
  ('normal control 6', [[300, 480, 600, 300, 510, 510, 510], 2000], [2400, 780, 30, 121000]),
  ('normal control 7', [[300, 480, 600, 300, 480, 480, 480], 2000], [2400, 720, 0, 116000]),
  ('normal control 8', [[510, 480, 300, 510, 300, 480, 480], 30], [2400, 660, 0, 1695])]]
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: daily overtime band cap 1[480, 0, 180, 24150][480, 240, 180, 34500]Failed
regression variant: daily overtime band cap 2[2400, 720, 1, 105040][2400, 960, 1, 115900]Failed
partial repair guard 3[2400, 801, 181, 148631][2400, 1281, 181, 175631]Failed
boundary control 4[2400, 480, 0, 104000][2400, 480, 0, 104000]Passed
boundary control 5[9, 0, 0, 5][9, 0, 0, 5]Passed
normal control 6[2400, 600, 0, 94875][2400, 600, 0, 94875]Passed
normal control 7[2400, 451, 421, 1959][2400, 1411, 421, 2679]Failed
normal control 8[2400, 1411, 1, 112963][2400, 1651, 1, 121963]Failed

SHA-256 / d4097d8a6d4735b52511e5801e6a12e9cb5d99a42681e350ce53a7fb0872511a

3 / The verified repair

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

N = 1
observations = []
def solve(days, rate):
    reg = ot15 = ot2 = 0
    seventh = all(m > 0 for m in days)
    for d, m in enumerate(days):
        if d == 6 and seventh:
            ot15 += min(m, 480)
            ot2 += max(m - 480, 0)
            continue
        reg += min(m, 480)
        ot15 += min(max(m - 480, 0), 240)
        ot2 += max(m - 720, 0)
    excess = max(reg - 2400, 0)
    reg -= excess
    ot15 += excess
    units = reg * rate * 2 + ot15 * rate * 3 + ot2 * rate * 4
    pay = (units + 60) // 120
    return [reg, ot15, ot2, pay]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: daily overtime band cap 1', [[900, 0, 0, 0, 0, 0, 0], 1725], [480, 240, 180, 34500]),
  ('regression variant: daily overtime band cap 2', [[480, 0, 480, 721, 720, 240, 720], 1810],
   [2400, 960, 1, 115900]),
  ('partial repair guard 3', [[900, 720, 721, 600, 201, 720, 0], 2250], [2400, 1281, 181, 175631]),
  ('boundary control 4', [[480, 480, 480, 480, 480, 480, 0], 2000], [2400, 480, 0, 104000]),
  ('boundary control 5', [[9, 0, 0, 0, 0, 0, 0], 30], [9, 0, 0, 5]),
  ('normal control 6', [[600, 510, 300, 300, 510, 480, 300], 1725], [2400, 600, 0, 94875]),
  ('normal control 7', [[900, 721, 0, 900, 481, 450, 780], 30], [2400, 1411, 421, 2679]),
  ('normal control 8', [[600, 450, 721, 600, 481, 720, 480], 1500], [2400, 1651, 1, 121963])],
 [('regression: daily overtime band cap 1', [[481, 119, 900, 256, 240, 20, 480], 2000],
   [1595, 721, 180, 101217]),
  ('regression variant: daily overtime band cap 2', [[0, 240, 0, 540, 780, 0, 0], 30], [1200, 300, 60, 885]),
  ('partial repair guard 3', [[450, 0, 908, 480, 721, 540, 540], 2250], [2400, 1050, 189, 163238]),
  ('boundary control 4', [[0, 480, 480, 480, 480, 480, 480], 1500], [2400, 480, 0, 78000]),
  ('boundary control 5', [[480, 480, 480, 480, 480, 480, 0], 2000], [2400, 480, 0, 104000]),
  ('normal control 6', [[780, 480, 0, 240, 0, 0, 481], 1500], [1680, 241, 60, 54038]),
  ('normal control 7', [[510, 510, 480, 600, 480, 600, 480], 2000], [2400, 1260, 0, 143000]),
  ('normal control 8', [[721, 150, 480, 450, 504, 540, 450], 30], [2400, 894, 1, 1872])],
 [('regression: daily overtime band cap 1', [[540, 450, 450, 900, 720, 240, 540], 2250],
   [2400, 1200, 240, 175500]),
  ('regression variant: daily overtime band cap 2', [[480, 721, 240, 481, 498, 450, 481], 1725],
   [2400, 949, 2, 110041]),
  ('partial repair guard 3', [[450, 780, 450, 480, 481, 481, 0], 1500], [2400, 662, 60, 87825]),
  ('boundary control 4', [[27, 0, 0, 0, 0, 0, 0], 30], [27, 0, 0, 14]),
  ('boundary control 5', [[0, 480, 480, 480, 480, 480, 480], 1500], [2400, 480, 0, 78000]),
  ('normal control 6', [[480, 480, 480, 480, 300, 480, 510], 90], [2400, 780, 30, 5445]),
  ('normal control 7', [[0, 37, 780, 0, 240, 240, 240], 30], [1237, 240, 60, 859]),
  ('normal control 8', [[900, 600, 900, 481, 737, 381, 780], 1500], [2400, 1702, 677, 157675])],
 [('regression: daily overtime band cap 1', [[0, 721, 240, 480, 900, 480, 0], 2250],
   [2160, 480, 181, 121575]),
  ('regression variant: daily overtime band cap 2', [[480, 600, 600, 720, 480, 780, 540], 2000],
   [2400, 1680, 120, 172000]),
  ('partial repair guard 3', [[335, 540, 0, 900, 0, 540, 240], 1725], [2015, 360, 180, 83806]),
  ('boundary control 4', [[600, 600, 600, 600, 600, 0, 0], 1500], [2400, 600, 0, 82500]),
  ('boundary control 5', [[27, 0, 0, 0, 0, 0, 0], 30], [27, 0, 0, 14]),
  ('normal control 6', [[721, 150, 480, 450, 504, 540, 450], 30], [2400, 894, 1, 1872]),
  ('normal control 7', [[510, 480, 300, 300, 600, 300, 510], 1725], [2340, 630, 30, 96169]),
  ('normal control 8', [[300, 600, 510, 600, 510, 600, 600], 1810], [2400, 1200, 120, 133940])],
 [('regression: daily overtime band cap 1', [[780, 780, 721, 450, 0, 0, 0], 2250], [1890, 720, 121, 120450]),
  ('regression variant: daily overtime band cap 2', [[480, 480, 481, 946, 720, 540, 450], 2000],
   [2400, 1471, 226, 168617]),
  ('partial repair guard 3', [[0, 14, 481, 481, 720, 720, 900], 2250], [2400, 736, 180, 144900]),
  ('boundary control 4', [[540, 540, 540, 540, 540, 540, 60], 2000], [2400, 900, 0, 125000]),
  ('boundary control 5', [[600, 600, 600, 600, 600, 0, 0], 1500], [2400, 600, 0, 82500]),
  ('normal control 6', [[300, 480, 600, 300, 510, 510, 510], 2000], [2400, 780, 30, 121000]),
  ('normal control 7', [[300, 480, 600, 300, 480, 480, 480], 2000], [2400, 720, 0, 116000]),
  ('normal control 8', [[510, 480, 300, 510, 300, 480, 480], 30], [2400, 660, 0, 1695])]]
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: daily overtime band cap 1[480, 240, 180, 34500][480, 240, 180, 34500]Passed
regression variant: daily overtime band cap 2[2400, 960, 1, 115900][2400, 960, 1, 115900]Passed
partial repair guard 3[2400, 1281, 181, 175631][2400, 1281, 181, 175631]Passed
boundary control 4[2400, 480, 0, 104000][2400, 480, 0, 104000]Passed
boundary control 5[9, 0, 0, 5][9, 0, 0, 5]Passed
normal control 6[2400, 600, 0, 94875][2400, 600, 0, 94875]Passed
normal control 7[2400, 1411, 421, 2679][2400, 1411, 421, 2679]Passed
normal control 8[2400, 1651, 1, 121963][2400, 1651, 1, 121963]Passed

SHA-256 / 9c0c6a2344a7cdb173aee05c08ab7b7011d6f19d2e92de261a8233df53604077

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

Case digest / ba724f2a144fa0a063c545e84f324593271e19e9e683f27965c6c1ae56939ca9