FA-74821 / Experiment statistics / Open access
Switchback carryover washout: The first block inherits the last scheduled arm · case 01
Opening minutes of the experiment are washed out as if a switch had occurred.
ROOT CAUSE
Block 0 wraps around to schedule[-1] as its previous arm.
VERIFIED REPAIR
Treat block 0 as having no previous arm.
Unsuccessful approach: Comparing against the next block looks forward instead of backward.
Case contract
Time is split into blocks of block_len minutes; block b runs schedule[b % len(schedule)]. Events [minute, value] in the first washout minutes of a block are excluded only when that block's arm differs from the previous block's arm (block 0 has no previous block). Return [mean A, mean B, excluded count] with None for an arm without events.
Why this case matters
Switchback experiments on marketplaces must discard carryover right after each switch.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events, block_len, washout, schedule):
sums = {'A': 0.0, 'B': 0.0}
counts = {'A': 0, 'B': 0}
excluded = 0
for minute, value in events:
b = minute // block_len
arm = schedule[b % len(schedule)]
prev = schedule[(b - 1) % len(schedule)]
if prev != arm and minute - b * block_len < washout:
excluded += 1
continue
sums[arm] += value
counts[arm] += 1
means = [round(sums[k] / counts[k], 6) if counts[k] else None for k in ('A', 'B')]
return means + [excluded]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 1]),
('washout after a switch', [[[31, 5], [45, 2]], 30, 10, ['A', 'B']], [None, 2.0, 1]),
('washout boundary is exclusive', [[[40, 7], [39, 1]], 30, 10, ['A', 'B']], [None, 7.0, 1]),
('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('switchback sample 1',
[[[99, 2], [56, 1], [112, 1], [41, 10], [149, 2], [27, 1], [159, 1]], 60, 5, ['B', 'A', 'A', 'B']],
[1.5, 4.0, 0]),
('switchback sample 2',
[[[0, 3], [104, 10], [69, 3], [65, 10], [45, 10]], 30, 10, ['A', 'A', 'B']],
[7.666667, None, 2])],
[('washout after a switch', [[[31, 5], [45, 2]], 30, 10, ['A', 'B']], [None, 2.0, 1]),
('washout boundary is exclusive', [[[40, 7], [39, 1]], 30, 10, ['A', 'B']], [None, 7.0, 1]),
('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('switchback sample 6',
[[[208, 2], [14, 10], [126, 1], [28, 10], [215, 2], [149, 3], [156, 2]], 60, 10, ['A', 'A', 'B']],
[6.0, 2.5, 1]),
('switchback sample 21', [[[14, 10]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1]),
('switchback sample 41',
[[[96, 3], [9, 1], [43, 2], [66, 3], [91, 2], [7, 10], [4, 1], [9, 3]], 30, 10, ['A', 'B', 'B']],
[3.75, 2.5, 2])],
[('washout boundary is exclusive', [[[40, 7], [39, 1]], 30, 10, ['A', 'B']], [None, 7.0, 1]),
('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('switchback sample 2',
[[[0, 3], [104, 10], [69, 3], [65, 10], [45, 10]], 30, 10, ['A', 'A', 'B']],
[7.666667, None, 2]),
('switchback sample 11',
[[[1, 1], [35, 3], [7, 10], [11, 2], [26, 3]], 10, 10, ['A', 'B']],
[5.5, None, 3]),
('switchback sample 44', [[[11, 3], [37, 10], [38, 10], [16, 1]], 10, 5, ['A', 'B', 'B']], [10.0, 1.0, 1])],
[('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('late minute in a block rounds down', [[[59, 6]], 30, 0, ['A', 'B']], [None, 6.0, 0]),
('switchback sample 16', [[[104, 10], [75, 1], [9, 3]], 30, 10, ['A', 'A', 'B']], [6.5, 1.0, 0]),
('switchback sample 42',
[[[8, 1], [37, 2], [27, 1], [1, 10], [27, 3], [38, 10]], 10, 5, ['A', 'B', 'B']],
[5.75, 2.0, 0]),
('switchback sample 59',
[[[20, 3], [23, 1], [39, 3], [2, 1], [7, 1], [30, 1], [27, 10]], 10, 5, ['A', 'A', 'B']],
[1.666667, 10.0, 3])],
[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 1]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('late minute in a block rounds down', [[[59, 6]], 30, 0, ['A', 'B']], [None, 6.0, 0]),
('switchback sample 9',
[[[91, 10], [112, 10], [87, 10], [7, 3], [110, 1], [37, 10], [43, 2]], 30, 10, ['A', 'B']],
[6.5, 4.333333, 2]),
('switchback sample 18', [[[5, 3], [24, 1], [24, 10], [38, 1]], 10, 10, ['A', 'B']], [3.0, None, 3]),
('switchback sample 21', [[[14, 10]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1])]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| no washout when the arm repeats | [5.0, None, 1] | [5.0, None, 1] | Passed |
| washout after a switch | [None, 2.0, 1] | [None, 2.0, 1] | Passed |
| washout boundary is exclusive | [None, 7.0, 1] | [None, 7.0, 1] | Passed |
| block boundary minute starts the new block | [3.0, 4.0, 0] | [3.0, 4.0, 0] | Passed |
| opening minutes are kept when the schedule ends on the other arm | [None, 6.0, 1] | [4.0, 6.0, 0] | Failed |
| first block has no carryover | [9.0, 3.0, 0] | [9.0, 3.0, 0] | Passed |
| switchback sample 1 | [1.5, 4.0, 0] | [1.5, 4.0, 0] | Passed |
| switchback sample 2 | [10.0, None, 3] | [7.666667, None, 2] | Failed |
SHA-256 / 7026146a27cd949b0ed4b9edc66b7dcb421d90b1948fb96ebdcaf6d78c0db392
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events, block_len, washout, schedule):
sums = {'A': 0.0, 'B': 0.0}
counts = {'A': 0, 'B': 0}
excluded = 0
for minute, value in events:
b = minute // block_len
arm = schedule[b % len(schedule)]
prev = schedule[(b + 1) % len(schedule)]
if prev != arm and minute - b * block_len < washout:
excluded += 1
continue
sums[arm] += value
counts[arm] += 1
means = [round(sums[k] / counts[k], 6) if counts[k] else None for k in ('A', 'B')]
return means + [excluded]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 1]),
('washout after a switch', [[[31, 5], [45, 2]], 30, 10, ['A', 'B']], [None, 2.0, 1]),
('washout boundary is exclusive', [[[40, 7], [39, 1]], 30, 10, ['A', 'B']], [None, 7.0, 1]),
('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('switchback sample 1',
[[[99, 2], [56, 1], [112, 1], [41, 10], [149, 2], [27, 1], [159, 1]], 60, 5, ['B', 'A', 'A', 'B']],
[1.5, 4.0, 0]),
('switchback sample 2',
[[[0, 3], [104, 10], [69, 3], [65, 10], [45, 10]], 30, 10, ['A', 'A', 'B']],
[7.666667, None, 2])],
[('washout after a switch', [[[31, 5], [45, 2]], 30, 10, ['A', 'B']], [None, 2.0, 1]),
('washout boundary is exclusive', [[[40, 7], [39, 1]], 30, 10, ['A', 'B']], [None, 7.0, 1]),
('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('switchback sample 6',
[[[208, 2], [14, 10], [126, 1], [28, 10], [215, 2], [149, 3], [156, 2]], 60, 10, ['A', 'A', 'B']],
[6.0, 2.5, 1]),
('switchback sample 21', [[[14, 10]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1]),
('switchback sample 41',
[[[96, 3], [9, 1], [43, 2], [66, 3], [91, 2], [7, 10], [4, 1], [9, 3]], 30, 10, ['A', 'B', 'B']],
[3.75, 2.5, 2])],
[('washout boundary is exclusive', [[[40, 7], [39, 1]], 30, 10, ['A', 'B']], [None, 7.0, 1]),
('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('switchback sample 2',
[[[0, 3], [104, 10], [69, 3], [65, 10], [45, 10]], 30, 10, ['A', 'A', 'B']],
[7.666667, None, 2]),
('switchback sample 11',
[[[1, 1], [35, 3], [7, 10], [11, 2], [26, 3]], 10, 10, ['A', 'B']],
[5.5, None, 3]),
('switchback sample 44', [[[11, 3], [37, 10], [38, 10], [16, 1]], 10, 5, ['A', 'B', 'B']], [10.0, 1.0, 1])],
[('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('late minute in a block rounds down', [[[59, 6]], 30, 0, ['A', 'B']], [None, 6.0, 0]),
('switchback sample 16', [[[104, 10], [75, 1], [9, 3]], 30, 10, ['A', 'A', 'B']], [6.5, 1.0, 0]),
('switchback sample 42',
[[[8, 1], [37, 2], [27, 1], [1, 10], [27, 3], [38, 10]], 10, 5, ['A', 'B', 'B']],
[5.75, 2.0, 0]),
('switchback sample 59',
[[[20, 3], [23, 1], [39, 3], [2, 1], [7, 1], [30, 1], [27, 10]], 10, 5, ['A', 'A', 'B']],
[1.666667, 10.0, 3])],
[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 1]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('late minute in a block rounds down', [[[59, 6]], 30, 0, ['A', 'B']], [None, 6.0, 0]),
('switchback sample 9',
[[[91, 10], [112, 10], [87, 10], [7, 3], [110, 1], [37, 10], [43, 2]], 30, 10, ['A', 'B']],
[6.5, 4.333333, 2]),
('switchback sample 18', [[[5, 3], [24, 1], [24, 10], [38, 1]], 10, 10, ['A', 'B']], [3.0, None, 3]),
('switchback sample 21', [[[14, 10]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1])]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| no washout when the arm repeats | [None, None, 2] | [5.0, None, 1] | Failed |
| washout after a switch | [None, 2.0, 1] | [None, 2.0, 1] | Passed |
| washout boundary is exclusive | [None, 7.0, 1] | [None, 7.0, 1] | Passed |
| block boundary minute starts the new block | [3.0, 4.0, 0] | [3.0, 4.0, 0] | Passed |
| opening minutes are kept when the schedule ends on the other arm | [None, 6.0, 1] | [4.0, 6.0, 0] | Failed |
| first block has no carryover | [9.0, None, 1] | [9.0, 3.0, 0] | Failed |
| switchback sample 1 | [1.5, 4.0, 0] | [1.5, 4.0, 0] | Passed |
| switchback sample 2 | [7.666667, None, 2] | [7.666667, None, 2] | Passed |
SHA-256 / 98ee2dc95592796fc7980b06310af6328b92f902149c1d2af5fc5a31bcb4683d
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
N = 1
observations = []
def solve(events, block_len, washout, schedule):
sums = {'A': 0.0, 'B': 0.0}
counts = {'A': 0, 'B': 0}
excluded = 0
for minute, value in events:
b = minute // block_len
arm = schedule[b % len(schedule)]
prev = schedule[(b - 1) % len(schedule)] if b > 0 else arm
if prev != arm and minute - b * block_len < washout:
excluded += 1
continue
sums[arm] += value
counts[arm] += 1
means = [round(sums[k] / counts[k], 6) if counts[k] else None for k in ('A', 'B')]
return means + [excluded]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 1]),
('washout after a switch', [[[31, 5], [45, 2]], 30, 10, ['A', 'B']], [None, 2.0, 1]),
('washout boundary is exclusive', [[[40, 7], [39, 1]], 30, 10, ['A', 'B']], [None, 7.0, 1]),
('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('switchback sample 1',
[[[99, 2], [56, 1], [112, 1], [41, 10], [149, 2], [27, 1], [159, 1]], 60, 5, ['B', 'A', 'A', 'B']],
[1.5, 4.0, 0]),
('switchback sample 2',
[[[0, 3], [104, 10], [69, 3], [65, 10], [45, 10]], 30, 10, ['A', 'A', 'B']],
[7.666667, None, 2])],
[('washout after a switch', [[[31, 5], [45, 2]], 30, 10, ['A', 'B']], [None, 2.0, 1]),
('washout boundary is exclusive', [[[40, 7], [39, 1]], 30, 10, ['A', 'B']], [None, 7.0, 1]),
('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('switchback sample 6',
[[[208, 2], [14, 10], [126, 1], [28, 10], [215, 2], [149, 3], [156, 2]], 60, 10, ['A', 'A', 'B']],
[6.0, 2.5, 1]),
('switchback sample 21', [[[14, 10]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1]),
('switchback sample 41',
[[[96, 3], [9, 1], [43, 2], [66, 3], [91, 2], [7, 10], [4, 1], [9, 3]], 30, 10, ['A', 'B', 'B']],
[3.75, 2.5, 2])],
[('washout boundary is exclusive', [[[40, 7], [39, 1]], 30, 10, ['A', 'B']], [None, 7.0, 1]),
('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('switchback sample 2',
[[[0, 3], [104, 10], [69, 3], [65, 10], [45, 10]], 30, 10, ['A', 'A', 'B']],
[7.666667, None, 2]),
('switchback sample 11',
[[[1, 1], [35, 3], [7, 10], [11, 2], [26, 3]], 10, 10, ['A', 'B']],
[5.5, None, 3]),
('switchback sample 44', [[[11, 3], [37, 10], [38, 10], [16, 1]], 10, 5, ['A', 'B', 'B']], [10.0, 1.0, 1])],
[('block boundary minute starts the new block', [[[30, 4], [29, 3]], 30, 0, ['A', 'B']], [3.0, 4.0, 0]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('late minute in a block rounds down', [[[59, 6]], 30, 0, ['A', 'B']], [None, 6.0, 0]),
('switchback sample 16', [[[104, 10], [75, 1], [9, 3]], 30, 10, ['A', 'A', 'B']], [6.5, 1.0, 0]),
('switchback sample 42',
[[[8, 1], [37, 2], [27, 1], [1, 10], [27, 3], [38, 10]], 10, 5, ['A', 'B', 'B']],
[5.75, 2.0, 0]),
('switchback sample 59',
[[[20, 3], [23, 1], [39, 3], [2, 1], [7, 1], [30, 1], [27, 10]], 10, 5, ['A', 'A', 'B']],
[1.666667, 10.0, 3])],
[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 1]),
('opening minutes are kept when the schedule ends on the other arm',
[[[1, 4], [40, 6]], 30, 5, ['A', 'B']],
[4.0, 6.0, 0]),
('first block has no carryover', [[[2, 3], [35, 9]], 30, 5, ['B', 'A', 'A', 'B']], [9.0, 3.0, 0]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('late minute in a block rounds down', [[[59, 6]], 30, 0, ['A', 'B']], [None, 6.0, 0]),
('switchback sample 9',
[[[91, 10], [112, 10], [87, 10], [7, 3], [110, 1], [37, 10], [43, 2]], 30, 10, ['A', 'B']],
[6.5, 4.333333, 2]),
('switchback sample 18', [[[5, 3], [24, 1], [24, 10], [38, 1]], 10, 10, ['A', 'B']], [3.0, None, 3]),
('switchback sample 21', [[[14, 10]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1])]]
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 fixture | Actual | Expected | Outcome |
|---|---|---|---|
| no washout when the arm repeats | [5.0, None, 1] | [5.0, None, 1] | Passed |
| washout after a switch | [None, 2.0, 1] | [None, 2.0, 1] | Passed |
| washout boundary is exclusive | [None, 7.0, 1] | [None, 7.0, 1] | Passed |
| block boundary minute starts the new block | [3.0, 4.0, 0] | [3.0, 4.0, 0] | Passed |
| opening minutes are kept when the schedule ends on the other arm | [4.0, 6.0, 0] | [4.0, 6.0, 0] | Passed |
| first block has no carryover | [9.0, 3.0, 0] | [9.0, 3.0, 0] | Passed |
| switchback sample 1 | [1.5, 4.0, 0] | [1.5, 4.0, 0] | Passed |
| switchback sample 2 | [7.666667, None, 2] | [7.666667, None, 2] | Passed |
SHA-256 / ee79849227b8b8ad317b1c0f90572c8489d17eda225946e14619b1e793a4253b
Verification & scope
A deterministic toy experiment-analysis model with a stipulated contract; results are rounded and are not a substitute for a validated statistics package. 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:49:00.284184+00:00.
Case digest / 49c36e0fd8206f01feaaa7ba6508ab068907d2bbd94d9c30ba7c852d5d2f2a91