FA-65266 / Epidemic compartment models / Open access
Delay-adjusted case fatality ratio: naive denominator · case 01
Naive CFR is understated by treating deaths as additional cases.
ROOT CAUSE
Deaths are added to the case denominator.
VERIFIED REPAIR
Restore the naive denominator rule: `naive = total_deaths / total_cases`.
Unsuccessful approach: Using the peak daily count is not the cumulative denominator.
Case contract
naive CFR = sum(deaths)/sum(cases); delay_pmf[j] is the probability that death occurs j days after onset (j from 0); known = sum_t sum_j cases[t-j]*pmf[j] over t in the series; adjusted = min(sum(deaths)/known, 1); return [naive rounded 6, adjusted rounded 6 or None when known is 0]; None when there are no cases.
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(cases, deaths, delay_pmf):
total_cases = sum(cases)
total_deaths = sum(deaths)
if total_cases <= 0:
return None
naive = total_deaths / (total_cases + total_deaths)
known = 0.0
for t in range(len(cases)):
for j, f in enumerate(delay_pmf):
if t - j >= 0:
known += cases[t - j] * f
if known <= 0:
return [round(naive, 6), None]
adjusted = min(total_deaths / known, 1.0)
return [round(naive, 6), round(adjusted, 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('regression: stable epidemic',
([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
[0.03, 0.036735]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667])],
[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])],
[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('regression: stable epidemic',
([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
[0.03, 0.036735]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])],
[('regression: stable epidemic',
([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
[0.03, 0.036735]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])],
[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])]]
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: growing epidemic | [0.025157, 0.072727] | [0.025806, 0.072727] | Failed |
| regression: stable epidemic | [0.029126, 0.036735] | [0.03, 0.036735] | Failed |
| control: long delay no outcomes | [0.0, None] | [0.0, None] | Passed |
| control: no cases | None | None | Passed |
| regression: adjusted exceeds one | [0.071429, 1.0] | [0.076923, 1.0] | Failed |
| regression: immediate deaths | [0.085366, 0.093333] | [0.093333, 0.093333] | Failed |
| regression: single day | [0.02439, 0.041667] | [0.025, 0.041667] | Failed |
SHA-256 / cf2d37f07ab1b9cb10967b06a980dd3dcb434103f4cb560a2206a13c86431c3a
2 / The unsuccessful fix
Exit 1"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(cases, deaths, delay_pmf):
total_cases = sum(cases)
total_deaths = sum(deaths)
if total_cases <= 0:
return None
naive = total_deaths / max(cases)
known = 0.0
for t in range(len(cases)):
for j, f in enumerate(delay_pmf):
if t - j >= 0:
known += cases[t - j] * f
if known <= 0:
return [round(naive, 6), None]
adjusted = min(total_deaths / known, 1.0)
return [round(naive, 6), round(adjusted, 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('regression: stable epidemic',
([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
[0.03, 0.036735]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667])],
[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])],
[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('regression: stable epidemic',
([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
[0.03, 0.036735]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])],
[('regression: stable epidemic',
([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
[0.03, 0.036735]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])],
[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])]]
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: growing epidemic | [0.05, 0.072727] | [0.025806, 0.072727] | Failed |
| regression: stable epidemic | [0.18, 0.036735] | [0.03, 0.036735] | Failed |
| control: long delay no outcomes | [0.0, None] | [0.0, None] | Passed |
| control: no cases | None | None | Passed |
| regression: adjusted exceeds one | [0.08, 1.0] | [0.076923, 1.0] | Failed |
| regression: immediate deaths | [0.233333, 0.093333] | [0.093333, 0.093333] | Failed |
| regression: single day | [0.025, 0.041667] | [0.025, 0.041667] | Passed |
SHA-256 / 0ab2f970f56eb2550951cba87edcbf65154e1a9b8f48d5d1319b538e0ab311c6
3 / The verified repair
Exit 0"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(cases, deaths, delay_pmf):
total_cases = sum(cases)
total_deaths = sum(deaths)
if total_cases <= 0:
return None
naive = total_deaths / total_cases
known = 0.0
for t in range(len(cases)):
for j, f in enumerate(delay_pmf):
if t - j >= 0:
known += cases[t - j] * f
if known <= 0:
return [round(naive, 6), None]
adjusted = min(total_deaths / known, 1.0)
return [round(naive, 6), round(adjusted, 6)]
def check(label, actual, expected):
observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('regression: stable epidemic',
([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
[0.03, 0.036735]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667])],
[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])],
[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('regression: stable epidemic',
([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
[0.03, 0.036735]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])],
[('regression: stable epidemic',
([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
[0.03, 0.036735]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])],
[('regression: growing epidemic',
([10, 20, 40, 80, 160], [0, 0, 1, 2, 5], [0.1, 0.3, 0.4, 0.2]),
[0.025806, 0.072727]),
('control: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
('control: no cases', ([0, 0, 0], [0, 0, 0], [0.5, 0.5]), None),
('regression: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
('regression: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
('regression: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
('regression: late surge',
([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
[0.024242, 0.111111])]]
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: growing epidemic | [0.025806, 0.072727] | [0.025806, 0.072727] | Passed |
| regression: stable epidemic | [0.03, 0.036735] | [0.03, 0.036735] | Passed |
| control: long delay no outcomes | [0.0, None] | [0.0, None] | Passed |
| control: no cases | None | None | Passed |
| regression: adjusted exceeds one | [0.076923, 1.0] | [0.076923, 1.0] | Passed |
| regression: immediate deaths | [0.093333, 0.093333] | [0.093333, 0.093333] | Passed |
| regression: single day | [0.025, 0.041667] | [0.025, 0.041667] | Passed |
SHA-256 / 01ee2c9d630e6727e1687eea9652a6ffb68915f863ebe60f5a37a8cf8474002c
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:32.382363+00:00.
Case digest / b60b6ac56c8f7bc7f090df7f89a39233c16e40e0835483e7e306089451f567e0