FAILURE MAP
← Case archive

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.

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

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 fixtureActualExpectedOutcome
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 fixtureActualExpectedOutcome
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 fixtureActualExpectedOutcome
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