FAILURE MAP
← Case archive

FA-94231 / Shift rostering labor rules / Open access

Overlapping callouts paid twice · case 01

Two overlapping callout records pay the shared minutes at full rate twice.

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

ROOT CAUSE

Callout minutes are summed per record instead of over their union.

VERIFIED REPAIR

Pay the union of callout minutes once.

Unsuccessful approach: Paying the span from first callout start to last end also pays the idle gaps between callouts.

Case contract

Standby window [start, end], callout intervals and hourly rate in cents; minutes from Monday 00:00. Callout minutes (union of callouts, inside or outside standby) pay full rate. Standby minutes not in a callout pay a third of rate on weekdays and half on Saturday/Sunday (days 5-6). Return total cents, computed exactly and rounded half up once.

Why this case matters

On-call allowances interact with callout pay; double payment and wrong day attribution are common.

1 / The failure

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

N = 1
observations = []
def solve(standby, callouts, rate):
    s0, e0 = standby
    busy = set()
    for s, e in callouts:
        busy.update(range(s, e))
    units = 0
    for t in range(s0, e0):
        if t in busy:
            continue
        units += 20 if (t // 1440) % 7 < 5 else 30
    units += 60 * sum(e - s for s, e in callouts)
    return (units * rate + 1800) // 3600
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: overlapping callouts 1', [[0, 60], [[0, 30], [15, 45]], 1800], 1500),
  ('regression variant: overlapping callouts 2',
   [[4200, 4800], [[4456, 4576], [4414, 4504], [4798, 4828]], 3000], 16867),
  ('partial repair guard 3', [[5400, 5640], [[5640, 5760], [5348, 5468]], 1999], 9906),
  ('boundary control 4', [[7140, 7260], [[7190, 7250]], 2400], 3267),
  ('boundary control 5', [[7200, 7260], [], 1800], 900),
  ('normal control 6', [[1080, 1920], [[1692, 1752], [1402, 1492]], 1800], 11400),
  ('normal control 7', [[2520, 3960], [[3928, 4018]], 2400], 22373),
  ('normal control 8', [[1440, 2880], [[2512, 2542], [2250, 2310]], 1800], 16200)],
 [('regression: overlapping callouts 1', [[5340, 5940], [[5348, 5378], [5293, 5353]], 1999], 9073),
  ('regression variant: overlapping callouts 2',
   [[2760, 3600], [[2911, 2971], [3430, 3520], [2914, 3034]], 1800], 12660),
  ('partial repair guard 3', [[8220, 8820], [[8187, 8247], [8269, 8299]], 1800], 10845),
  ('boundary control 4', [[0, 60], [], 1800], 600), ('boundary control 5', [[0, 3], [], 30], 1),
  ('normal control 6', [[6840, 8280], [], 1999], 21989),
  ('normal control 7', [[5760, 6360], [[5953, 6043]], 1999], 8662),
  ('normal control 8', [[2520, 3360], [[3252, 3282], [3025, 3145], [3101, 3191]], 3000], 20533)],
 [('regression: overlapping callouts 1', [[8640, 8880], [[8660, 8750], [8748, 8838]], 1800], 6270),
  ('regression variant: overlapping callouts 2',
   [[3900, 4140], [[4088, 4178], [4121, 4181], [3949, 4039]], 2400], 8627),
  ('partial repair guard 3', [[6840, 7680], [[7144, 7264], [6829, 6919], [6824, 6854]], 1999], 16592),
  ('boundary control 4', [[7140, 7260], [[7190, 7250]], 2400], 3267),
  ('boundary control 5', [[7200, 7260], [], 1800], 900),
  ('normal control 6', [[6780, 7620], [[7470, 7560], [6740, 6830], [6903, 6963]], 1999], 16936),
  ('normal control 7', [[5340, 6180], [[5289, 5409], [5942, 6002], [5458, 5548]], 3000], 23850),
  ('normal control 8', [[5640, 6240], [[5947, 6007], [5613, 5733], [5788, 5908]], 2400], 16360)],
 [('regression: overlapping callouts 1', [[5760, 6000], [[5788, 5908], [5761, 5791]], 1800], 5340),
  ('regression variant: overlapping callouts 2',
   [[2520, 3120], [[2929, 3049], [2501, 2561], [2522, 2612]], 1800], 10810),
  ('partial repair guard 3', [[8520, 8760], [[8600, 8630], [8558, 8588]], 3000], 7500),
  ('boundary control 4', [[0, 60], [], 1800], 600), ('boundary control 5', [[0, 3], [], 30], 1),
  ('normal control 6', [[2520, 3120], [[3112, 3232], [2573, 2663]], 2400], 15093),
  ('normal control 7', [[7200, 8640], [[8288, 8348], [7751, 7871]], 1999], 26987),
  ('normal control 8', [[2760, 3600], [[2814, 2904]], 2400], 13600)],
 [('regression: overlapping callouts 1', [[8520, 8760], [[8576, 8696], [8730, 8850], [8520, 8580]], 2400],
   12520),
  ('regression variant: overlapping callouts 2',
   [[5760, 6000], [[5737, 5797], [5741, 5801], [5873, 5933]], 2400], 6813),
  ('partial repair guard 3', [[6780, 7620], [[7592, 7712], [6879, 6999], [7600, 7660]], 3000], 26800),
  ('boundary control 4', [[7140, 7260], [[7190, 7250]], 2400], 3267),
  ('boundary control 5', [[7200, 7260], [], 1800], 900),
  ('normal control 6', [[1320, 1560], [[1512, 1602], [1410, 1470]], 1800], 5820),
  ('normal control 7', [[2760, 3000], [[2993, 3083]], 1800], 5030),
  ('normal control 8', [[8220, 9060], [], 1999], 13993)]]
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: overlapping callouts 119501500Failed
regression variant: overlapping callouts 21926716867Failed
partial repair guard 399069906Passed
boundary control 432673267Passed
boundary control 5900900Passed
normal control 61140011400Passed
normal control 72237322373Passed
normal control 81620016200Passed

SHA-256 / 0f6ce2ca3435a01a49b6f9caffbb808c2c3b66e5f5b1551bb9322c02c514668b

2 / The unsuccessful fix

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

N = 1
observations = []
def solve(standby, callouts, rate):
    s0, e0 = standby
    busy = set()
    for s, e in callouts:
        busy.update(range(s, e))
    units = 0
    for t in range(s0, e0):
        if t in busy:
            continue
        units += 20 if (t // 1440) % 7 < 5 else 30
    units += 60 * (max((e for s, e in callouts), default=0) - min((s for s, e in callouts), default=0))
    return (units * rate + 1800) // 3600
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: overlapping callouts 1', [[0, 60], [[0, 30], [15, 45]], 1800], 1500),
  ('regression variant: overlapping callouts 2',
   [[4200, 4800], [[4456, 4576], [4414, 4504], [4798, 4828]], 3000], 16867),
  ('partial repair guard 3', [[5400, 5640], [[5640, 5760], [5348, 5468]], 1999], 9906),
  ('boundary control 4', [[7140, 7260], [[7190, 7250]], 2400], 3267),
  ('boundary control 5', [[7200, 7260], [], 1800], 900),
  ('normal control 6', [[1080, 1920], [[1692, 1752], [1402, 1492]], 1800], 11400),
  ('normal control 7', [[2520, 3960], [[3928, 4018]], 2400], 22373),
  ('normal control 8', [[1440, 2880], [[2512, 2542], [2250, 2310]], 1800], 16200)],
 [('regression: overlapping callouts 1', [[5340, 5940], [[5348, 5378], [5293, 5353]], 1999], 9073),
  ('regression variant: overlapping callouts 2',
   [[2760, 3600], [[2911, 2971], [3430, 3520], [2914, 3034]], 1800], 12660),
  ('partial repair guard 3', [[8220, 8820], [[8187, 8247], [8269, 8299]], 1800], 10845),
  ('boundary control 4', [[0, 60], [], 1800], 600), ('boundary control 5', [[0, 3], [], 30], 1),
  ('normal control 6', [[6840, 8280], [], 1999], 21989),
  ('normal control 7', [[5760, 6360], [[5953, 6043]], 1999], 8662),
  ('normal control 8', [[2520, 3360], [[3252, 3282], [3025, 3145], [3101, 3191]], 3000], 20533)],
 [('regression: overlapping callouts 1', [[8640, 8880], [[8660, 8750], [8748, 8838]], 1800], 6270),
  ('regression variant: overlapping callouts 2',
   [[3900, 4140], [[4088, 4178], [4121, 4181], [3949, 4039]], 2400], 8627),
  ('partial repair guard 3', [[6840, 7680], [[7144, 7264], [6829, 6919], [6824, 6854]], 1999], 16592),
  ('boundary control 4', [[7140, 7260], [[7190, 7250]], 2400], 3267),
  ('boundary control 5', [[7200, 7260], [], 1800], 900),
  ('normal control 6', [[6780, 7620], [[7470, 7560], [6740, 6830], [6903, 6963]], 1999], 16936),
  ('normal control 7', [[5340, 6180], [[5289, 5409], [5942, 6002], [5458, 5548]], 3000], 23850),
  ('normal control 8', [[5640, 6240], [[5947, 6007], [5613, 5733], [5788, 5908]], 2400], 16360)],
 [('regression: overlapping callouts 1', [[5760, 6000], [[5788, 5908], [5761, 5791]], 1800], 5340),
  ('regression variant: overlapping callouts 2',
   [[2520, 3120], [[2929, 3049], [2501, 2561], [2522, 2612]], 1800], 10810),
  ('partial repair guard 3', [[8520, 8760], [[8600, 8630], [8558, 8588]], 3000], 7500),
  ('boundary control 4', [[0, 60], [], 1800], 600), ('boundary control 5', [[0, 3], [], 30], 1),
  ('normal control 6', [[2520, 3120], [[3112, 3232], [2573, 2663]], 2400], 15093),
  ('normal control 7', [[7200, 8640], [[8288, 8348], [7751, 7871]], 1999], 26987),
  ('normal control 8', [[2760, 3600], [[2814, 2904]], 2400], 13600)],
 [('regression: overlapping callouts 1', [[8520, 8760], [[8576, 8696], [8730, 8850], [8520, 8580]], 2400],
   12520),
  ('regression variant: overlapping callouts 2',
   [[5760, 6000], [[5737, 5797], [5741, 5801], [5873, 5933]], 2400], 6813),
  ('partial repair guard 3', [[6780, 7620], [[7592, 7712], [6879, 6999], [7600, 7660]], 3000], 26800),
  ('boundary control 4', [[7140, 7260], [[7190, 7250]], 2400], 3267),
  ('boundary control 5', [[7200, 7260], [], 1800], 900),
  ('normal control 6', [[1320, 1560], [[1512, 1602], [1410, 1470]], 1800], 5820),
  ('normal control 7', [[2760, 3000], [[2993, 3083]], 1800], 5030),
  ('normal control 8', [[8220, 9060], [], 1999], 13993)]]
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: overlapping callouts 115001500Passed
regression variant: overlapping callouts 22796716867Failed
partial repair guard 3156379906Failed
boundary control 432673267Passed
boundary control 5900900Passed
normal control 61740011400Failed
normal control 72237322373Passed
normal control 82226016200Failed

SHA-256 / 823013e16fdc75085d54c11882744d84538c43cdb4d083e40b79346be6feaae4

3 / The verified repair

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

N = 1
observations = []
def solve(standby, callouts, rate):
    s0, e0 = standby
    busy = set()
    for s, e in callouts:
        busy.update(range(s, e))
    units = 0
    for t in range(s0, e0):
        if t in busy:
            continue
        units += 20 if (t // 1440) % 7 < 5 else 30
    units += 60 * len(busy)
    return (units * rate + 1800) // 3600
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: overlapping callouts 1', [[0, 60], [[0, 30], [15, 45]], 1800], 1500),
  ('regression variant: overlapping callouts 2',
   [[4200, 4800], [[4456, 4576], [4414, 4504], [4798, 4828]], 3000], 16867),
  ('partial repair guard 3', [[5400, 5640], [[5640, 5760], [5348, 5468]], 1999], 9906),
  ('boundary control 4', [[7140, 7260], [[7190, 7250]], 2400], 3267),
  ('boundary control 5', [[7200, 7260], [], 1800], 900),
  ('normal control 6', [[1080, 1920], [[1692, 1752], [1402, 1492]], 1800], 11400),
  ('normal control 7', [[2520, 3960], [[3928, 4018]], 2400], 22373),
  ('normal control 8', [[1440, 2880], [[2512, 2542], [2250, 2310]], 1800], 16200)],
 [('regression: overlapping callouts 1', [[5340, 5940], [[5348, 5378], [5293, 5353]], 1999], 9073),
  ('regression variant: overlapping callouts 2',
   [[2760, 3600], [[2911, 2971], [3430, 3520], [2914, 3034]], 1800], 12660),
  ('partial repair guard 3', [[8220, 8820], [[8187, 8247], [8269, 8299]], 1800], 10845),
  ('boundary control 4', [[0, 60], [], 1800], 600), ('boundary control 5', [[0, 3], [], 30], 1),
  ('normal control 6', [[6840, 8280], [], 1999], 21989),
  ('normal control 7', [[5760, 6360], [[5953, 6043]], 1999], 8662),
  ('normal control 8', [[2520, 3360], [[3252, 3282], [3025, 3145], [3101, 3191]], 3000], 20533)],
 [('regression: overlapping callouts 1', [[8640, 8880], [[8660, 8750], [8748, 8838]], 1800], 6270),
  ('regression variant: overlapping callouts 2',
   [[3900, 4140], [[4088, 4178], [4121, 4181], [3949, 4039]], 2400], 8627),
  ('partial repair guard 3', [[6840, 7680], [[7144, 7264], [6829, 6919], [6824, 6854]], 1999], 16592),
  ('boundary control 4', [[7140, 7260], [[7190, 7250]], 2400], 3267),
  ('boundary control 5', [[7200, 7260], [], 1800], 900),
  ('normal control 6', [[6780, 7620], [[7470, 7560], [6740, 6830], [6903, 6963]], 1999], 16936),
  ('normal control 7', [[5340, 6180], [[5289, 5409], [5942, 6002], [5458, 5548]], 3000], 23850),
  ('normal control 8', [[5640, 6240], [[5947, 6007], [5613, 5733], [5788, 5908]], 2400], 16360)],
 [('regression: overlapping callouts 1', [[5760, 6000], [[5788, 5908], [5761, 5791]], 1800], 5340),
  ('regression variant: overlapping callouts 2',
   [[2520, 3120], [[2929, 3049], [2501, 2561], [2522, 2612]], 1800], 10810),
  ('partial repair guard 3', [[8520, 8760], [[8600, 8630], [8558, 8588]], 3000], 7500),
  ('boundary control 4', [[0, 60], [], 1800], 600), ('boundary control 5', [[0, 3], [], 30], 1),
  ('normal control 6', [[2520, 3120], [[3112, 3232], [2573, 2663]], 2400], 15093),
  ('normal control 7', [[7200, 8640], [[8288, 8348], [7751, 7871]], 1999], 26987),
  ('normal control 8', [[2760, 3600], [[2814, 2904]], 2400], 13600)],
 [('regression: overlapping callouts 1', [[8520, 8760], [[8576, 8696], [8730, 8850], [8520, 8580]], 2400],
   12520),
  ('regression variant: overlapping callouts 2',
   [[5760, 6000], [[5737, 5797], [5741, 5801], [5873, 5933]], 2400], 6813),
  ('partial repair guard 3', [[6780, 7620], [[7592, 7712], [6879, 6999], [7600, 7660]], 3000], 26800),
  ('boundary control 4', [[7140, 7260], [[7190, 7250]], 2400], 3267),
  ('boundary control 5', [[7200, 7260], [], 1800], 900),
  ('normal control 6', [[1320, 1560], [[1512, 1602], [1410, 1470]], 1800], 5820),
  ('normal control 7', [[2760, 3000], [[2993, 3083]], 1800], 5030),
  ('normal control 8', [[8220, 9060], [], 1999], 13993)]]
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: overlapping callouts 115001500Passed
regression variant: overlapping callouts 21686716867Passed
partial repair guard 399069906Passed
boundary control 432673267Passed
boundary control 5900900Passed
normal control 61140011400Passed
normal control 72237322373Passed
normal control 81620016200Passed

SHA-256 / c4988b55a4e1cd74d4340c121b9a0bc1b9e97c06713a9c570f8ec7196fc04b0e

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

Case digest / d267b87001276fb36f779891f5ced2e5c7a1ec6c9f12dddc4c0953e741668e02