FA-74806 / Experiment statistics / Open access
Switchback carryover washout: Every block start is washed out · case 01
Data after repeated-arm blocks is discarded, wasting sample for no carryover.
ROOT CAUSE
The washout ignores whether the arm actually changed.
VERIFIED REPAIR
Exclude early events only in blocks whose arm differs from the previous block.
Unsuccessful approach: Skipping only block 0 still washes out blocks that repeat the previous arm.
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 b > 0 else arm
if 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])],
[('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 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 18', [[[5, 3], [24, 1], [24, 10], [38, 1]], 10, 10, ['A', 'B']], [3.0, None, 3])],
[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 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]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('switchback sample 11',
[[[1, 1], [35, 3], [7, 10], [11, 2], [26, 3]], 10, 10, ['A', 'B']],
[5.5, None, 3]),
('switchback sample 53',
[[[26, 10], [77, 10], [1, 10], [8, 10], [3, 1]], 60, 10, ['A', 'B']],
[7.75, 10.0, 0])],
[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 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 17',
[[[25, 2], [8, 10], [16, 1], [8, 10], [36, 1], [26, 10], [39, 3]], 10, 0, ['A', 'B', 'B']],
[6.0, 4.333333, 0])],
[('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 21', [[[14, 10]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1]),
('switchback sample 22', [[[16, 2]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1]),
('switchback sample 37',
[[[7, 2], [114, 1], [217, 3], [61, 3], [230, 10]], 60, 5, ['A', 'A', 'B']],
[3.8, None, 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 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 | [10.0, None, 3] | [7.666667, None, 2] | Failed |
SHA-256 / 6bdf0a237353ac4fe4cd54920273e1dbf2355e25028ad463bffdbe2408a235b0
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 b > 0 else arm
if b > 0 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])],
[('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 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 18', [[[5, 3], [24, 1], [24, 10], [38, 1]], 10, 10, ['A', 'B']], [3.0, None, 3])],
[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 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]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('switchback sample 11',
[[[1, 1], [35, 3], [7, 10], [11, 2], [26, 3]], 10, 10, ['A', 'B']],
[5.5, None, 3]),
('switchback sample 53',
[[[26, 10], [77, 10], [1, 10], [8, 10], [3, 1]], 60, 10, ['A', 'B']],
[7.75, 10.0, 0])],
[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 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 17',
[[[25, 2], [8, 10], [16, 1], [8, 10], [36, 1], [26, 10], [39, 3]], 10, 0, ['A', 'B', 'B']],
[6.0, 4.333333, 0])],
[('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 21', [[[14, 10]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1]),
('switchback sample 22', [[[16, 2]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1]),
('switchback sample 37',
[[[7, 2], [114, 1], [217, 3], [61, 3], [230, 10]], 60, 5, ['A', 'A', 'B']],
[3.8, None, 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 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 | [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 / 625dad8e37994c661d76e85ab984a047038f9e62bda38ccd78a752231a28b07c
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])],
[('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 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 18', [[[5, 3], [24, 1], [24, 10], [38, 1]], 10, 10, ['A', 'B']], [3.0, None, 3])],
[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 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]),
('second cycle wraps the schedule', [[[61, 8], [95, 2]], 30, 5, ['A', 'B']], [None, 2.0, 1]),
('switchback sample 11',
[[[1, 1], [35, 3], [7, 10], [11, 2], [26, 3]], 10, 10, ['A', 'B']],
[5.5, None, 3]),
('switchback sample 53',
[[[26, 10], [77, 10], [1, 10], [8, 10], [3, 1]], 60, 10, ['A', 'B']],
[7.75, 10.0, 0])],
[('no washout when the arm repeats', [[[31, 5], [65, 1]], 30, 10, ['A', 'A', 'B']], [5.0, None, 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 17',
[[[25, 2], [8, 10], [16, 1], [8, 10], [36, 1], [26, 10], [39, 3]], 10, 0, ['A', 'B', 'B']],
[6.0, 4.333333, 0])],
[('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 21', [[[14, 10]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1]),
('switchback sample 22', [[[16, 2]], 10, 10, ['B', 'A', 'A', 'B']], [None, None, 1]),
('switchback sample 37',
[[[7, 2], [114, 1], [217, 3], [61, 3], [230, 10]], 60, 5, ['A', 'A', 'B']],
[3.8, None, 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 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 / 977d1271d5208295b0d409ba2c7961e03a2076f4a8a54f520a146525c39111f6
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.273248+00:00.
Case digest / 67725030cf2dacc0130431266b89513c5cc681976cca4c0feb872292d787430b