FA-65156 / Epidemic compartment models / Open access
Epidemic growth rate, doubling time and R: log base · case 01
Growth rates are scaled by a constant factor and R is understated.
ROOT CAUSE
Counts are log-transformed with base 10 instead of natural logs.
VERIFIED REPAIR
Restore the log base rule: `math.log(c)) for d`.
Unsuccessful approach: Base-2 logs still mis-scale r relative to ln2/r and the R mapping.
Case contract
r is the least-squares slope of ln(count) against day index over days with positive counts (original day indices kept); doubling time ln2/r only when r>0 else None; latent time Tl=latent_fraction*Tg, infectious time Ti=Tg-Tl, R=(1+r*Tl)*(1+r*Ti); return [r rounded 6, doubling rounded 4, R rounded 6]; None with fewer than two positive days.
Why this case matters
Compartmental epidemic calculations drive outbreak forecasts, vaccine targets and hospital planning; a single wrong flow, rate conversion or boundary silently changes every downstream number.
1 / The failure
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(counts, generation_mean, latent_fraction):
pts = [(d, math.log10(c)) for d, c in enumerate(counts) if c > 0]
if len(pts) < 2:
return None
n = len(pts)
mx = sum(d for d, _ in pts) / n
my = sum(y for _, y in pts) / n
sxx = sum((d - mx) ** 2 for d, _ in pts)
sxy = sum((d - mx) * (y - my) for d, y in pts)
r = sxy / sxx
doubling = round(math.log(2) / r, 4) if r > 0 else None
tl = latent_fraction * generation_mean
ti = generation_mean - tl
rep = (1 + r * tl) * (1 + r * ti)
return [round(r, 6), doubling, round(rep, 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('regression: with reporting gap zero', ([2, 4, 0, 9, 13, 0, 30], 6.0, 0.5), [0.437065, 1.5859, 5.341617]),
('regression: declining', ([50, 41, 30, 26, 18, 15], 4.0, 0.3), [-0.246645, None, 0.217822]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592]),
('regression: no latency', ([10, 13, 17, 22, 28], 5.0, 0.0), [0.258533, 2.6811, 2.292666]),
('regression: two points', ([5, 9], 4.5, 0.25), [0.587787, 1.1792, 4.956834])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('regression: with reporting gap zero', ([2, 4, 0, 9, 13, 0, 30], 6.0, 0.5), [0.437065, 1.5859, 5.341617]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592]),
('regression: no latency', ([10, 13, 17, 22, 28], 5.0, 0.0), [0.258533, 2.6811, 2.292666]),
('regression: two points', ([5, 9], 4.5, 0.25), [0.587787, 1.1792, 4.956834])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('regression: with reporting gap zero', ([2, 4, 0, 9, 13, 0, 30], 6.0, 0.5), [0.437065, 1.5859, 5.341617]),
('regression: declining', ([50, 41, 30, 26, 18, 15], 4.0, 0.3), [-0.246645, None, 0.217822]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592]),
('regression: no latency', ([10, 13, 17, 22, 28], 5.0, 0.0), [0.258533, 2.6811, 2.292666]),
('regression: two points', ([5, 9], 4.5, 0.25), [0.587787, 1.1792, 4.956834])]]
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 |
|---|---|---|---|
| regression: noisy growth | [0.161698, 4.2867, 1.965367] | [0.372323, 1.8617, 3.693365] | Failed |
| regression: with reporting gap zero | [0.189815, 3.6517, 2.463155] | [0.437065, 1.5859, 5.341617] | Failed |
| regression: declining | [-0.107116, None, 0.610087] | [-0.246645, None, 0.217822] | Failed |
| control: flat | [0.0, None, 1.0] | [0.0, None, 1.0] | Passed |
| control: too few positives | None | None | Passed |
| regression: exact doubling per day | [0.30103, 2.3026, 2.106983] | [0.693147, 1.0, 4.160461] | Failed |
| regression: leading zeros | [0.345939, 2.0037, 4.828941] | [0.796555, 0.8702, 14.037592] | Failed |
SHA-256 / 23d51a66956f6fa07dac335ee71dd3d12f788e57d1f75a33c3108aa1623649a0
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(counts, generation_mean, latent_fraction):
pts = [(d, math.log2(c)) for d, c in enumerate(counts) if c > 0]
if len(pts) < 2:
return None
n = len(pts)
mx = sum(d for d, _ in pts) / n
my = sum(y for _, y in pts) / n
sxx = sum((d - mx) ** 2 for d, _ in pts)
sxy = sum((d - mx) * (y - my) for d, y in pts)
r = sxy / sxx
doubling = round(math.log(2) / r, 4) if r > 0 else None
tl = latent_fraction * generation_mean
ti = generation_mean - tl
rep = (1 + r * tl) * (1 + r * ti)
return [round(r, 6), doubling, round(rep, 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('regression: with reporting gap zero', ([2, 4, 0, 9, 13, 0, 30], 6.0, 0.5), [0.437065, 1.5859, 5.341617]),
('regression: declining', ([50, 41, 30, 26, 18, 15], 4.0, 0.3), [-0.246645, None, 0.217822]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592]),
('regression: no latency', ([10, 13, 17, 22, 28], 5.0, 0.0), [0.258533, 2.6811, 2.292666]),
('regression: two points', ([5, 9], 4.5, 0.25), [0.587787, 1.1792, 4.956834])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('regression: with reporting gap zero', ([2, 4, 0, 9, 13, 0, 30], 6.0, 0.5), [0.437065, 1.5859, 5.341617]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592]),
('regression: no latency', ([10, 13, 17, 22, 28], 5.0, 0.0), [0.258533, 2.6811, 2.292666]),
('regression: two points', ([5, 9], 4.5, 0.25), [0.587787, 1.1792, 4.956834])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('regression: with reporting gap zero', ([2, 4, 0, 9, 13, 0, 30], 6.0, 0.5), [0.437065, 1.5859, 5.341617]),
('regression: declining', ([50, 41, 30, 26, 18, 15], 4.0, 0.3), [-0.246645, None, 0.217822]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592]),
('regression: no latency', ([10, 13, 17, 22, 28], 5.0, 0.0), [0.258533, 2.6811, 2.292666]),
('regression: two points', ([5, 9], 4.5, 0.25), [0.587787, 1.1792, 4.956834])]]
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 |
|---|---|---|---|
| regression: noisy growth | [0.537149, 1.2904, 5.416921] | [0.372323, 1.8617, 3.693365] | Failed |
| regression: with reporting gap zero | [0.630551, 1.0993, 8.361657] | [0.437065, 1.5859, 5.341617] | Failed |
| regression: declining | [-0.355833, None, 0.002101] | [-0.246645, None, 0.217822] | Failed |
| control: flat | [0.0, None, 1.0] | [0.0, None, 1.0] | Passed |
| control: too few positives | None | None | Passed |
| regression: exact doubling per day | [1.0, 0.6931, 6.25] | [0.693147, 1.0, 4.160461] | Failed |
| regression: leading zeros | [1.149185, 0.6032, 24.574869] | [0.796555, 0.8702, 14.037592] | Failed |
SHA-256 / 0e299f5fef25cf9e097112f769a7dc8933442f85e9667416a6099cbbf6cbcf84
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(counts, generation_mean, latent_fraction):
pts = [(d, math.log(c)) for d, c in enumerate(counts) if c > 0]
if len(pts) < 2:
return None
n = len(pts)
mx = sum(d for d, _ in pts) / n
my = sum(y for _, y in pts) / n
sxx = sum((d - mx) ** 2 for d, _ in pts)
sxy = sum((d - mx) * (y - my) for d, y in pts)
r = sxy / sxx
doubling = round(math.log(2) / r, 4) if r > 0 else None
tl = latent_fraction * generation_mean
ti = generation_mean - tl
rep = (1 + r * tl) * (1 + r * ti)
return [round(r, 6), doubling, round(rep, 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('regression: with reporting gap zero', ([2, 4, 0, 9, 13, 0, 30], 6.0, 0.5), [0.437065, 1.5859, 5.341617]),
('regression: declining', ([50, 41, 30, 26, 18, 15], 4.0, 0.3), [-0.246645, None, 0.217822]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592]),
('regression: no latency', ([10, 13, 17, 22, 28], 5.0, 0.0), [0.258533, 2.6811, 2.292666]),
('regression: two points', ([5, 9], 4.5, 0.25), [0.587787, 1.1792, 4.956834])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('regression: with reporting gap zero', ([2, 4, 0, 9, 13, 0, 30], 6.0, 0.5), [0.437065, 1.5859, 5.341617]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592]),
('regression: no latency', ([10, 13, 17, 22, 28], 5.0, 0.0), [0.258533, 2.6811, 2.292666]),
('regression: two points', ([5, 9], 4.5, 0.25), [0.587787, 1.1792, 4.956834])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('regression: with reporting gap zero', ([2, 4, 0, 9, 13, 0, 30], 6.0, 0.5), [0.437065, 1.5859, 5.341617]),
('regression: declining', ([50, 41, 30, 26, 18, 15], 4.0, 0.3), [-0.246645, None, 0.217822]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592])],
[('regression: noisy growth', ([3, 5, 6, 10, 14, 19, 30], 5.0, 0.4), [0.372323, 1.8617, 3.693365]),
('control: flat', ([7, 7, 7, 7], 5.0, 0.5), [0.0, None, 1.0]),
('control: too few positives', ([0, 0, 4, 0], 5.0, 0.5), None),
('regression: exact doubling per day', ([1, 2, 4, 8, 16], 3.0, 0.5), [0.693147, 1.0, 4.160461]),
('regression: leading zeros', ([0, 0, 1, 3, 5, 12], 7.0, 0.6), [0.796555, 0.8702, 14.037592]),
('regression: no latency', ([10, 13, 17, 22, 28], 5.0, 0.0), [0.258533, 2.6811, 2.292666]),
('regression: two points', ([5, 9], 4.5, 0.25), [0.587787, 1.1792, 4.956834])]]
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 |
|---|---|---|---|
| regression: noisy growth | [0.372323, 1.8617, 3.693365] | [0.372323, 1.8617, 3.693365] | Passed |
| regression: with reporting gap zero | [0.437065, 1.5859, 5.341617] | [0.437065, 1.5859, 5.341617] | Passed |
| regression: declining | [-0.246645, None, 0.217822] | [-0.246645, None, 0.217822] | Passed |
| control: flat | [0.0, None, 1.0] | [0.0, None, 1.0] | Passed |
| control: too few positives | None | None | Passed |
| regression: exact doubling per day | [0.693147, 1.0, 4.160461] | [0.693147, 1.0, 4.160461] | Passed |
| regression: leading zeros | [0.796555, 0.8702, 14.037592] | [0.796555, 0.8702, 14.037592] | Passed |
SHA-256 / ab24af451f11396cf6aee6d4af248ceb30906cefd02e401737baf05c2a338a53
Verification & scope
Deterministic bounded teaching model with a stipulated contract; not a validated scientific or public-health modelling library. 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:47:31.361781+00:00.
Case digest / 40b317640f17a97f588800fa8d6b724a1611eed1f4cfe676f03f7b8c15bc537f