FAILURE MAP
← Case archive

FA-65261 / Epidemic compartment models / Open access

Delay-adjusted case fatality ratio: unresolved outcome result · case 01

With no resolved outcomes the adjusted CFR silently equals the naive one.

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

ROOT CAUSE

Missing adjustment falls back to the naive estimate.

VERIFIED REPAIR

Restore the unresolved outcome result rule: `return [round(naive, 6), None]`.

Unsuccessful approach: Reporting 0.0 claims a measured zero fatality ratio.

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
    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), round(naive, 6)]
    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 = [[('control: 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: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667])],
 [('control: 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: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: late surge',
   ([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
   [0.024242, 0.111111])],
 [('control: 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: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: late surge',
   ([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
   [0.024242, 0.111111])],
 [('control: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: late surge',
   ([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
   [0.024242, 0.111111])],
 [('control: 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: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: 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 fixtureActualExpectedOutcome
control: growing epidemic[0.025806, 0.072727][0.025806, 0.072727]Passed
control: stable epidemic[0.03, 0.036735][0.03, 0.036735]Passed
regression: long delay no outcomes[0.0, 0.0][0.0, None]Failed
control: no casesNoneNonePassed
control: adjusted exceeds one[0.076923, 1.0][0.076923, 1.0]Passed
control: immediate deaths[0.093333, 0.093333][0.093333, 0.093333]Passed
control: single day[0.025, 0.041667][0.025, 0.041667]Passed

SHA-256 / d2562bae779bca794c97e12593f7f617f902a42fa9aa0c0edbe4fa4f68e6e7e1

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 / 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), 0.0]
    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 = [[('control: 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: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667])],
 [('control: 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: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: late surge',
   ([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
   [0.024242, 0.111111])],
 [('control: 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: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: late surge',
   ([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
   [0.024242, 0.111111])],
 [('control: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: late surge',
   ([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
   [0.024242, 0.111111])],
 [('control: 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: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: 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 fixtureActualExpectedOutcome
control: growing epidemic[0.025806, 0.072727][0.025806, 0.072727]Passed
control: stable epidemic[0.03, 0.036735][0.03, 0.036735]Passed
regression: long delay no outcomes[0.0, 0.0][0.0, None]Failed
control: no casesNoneNonePassed
control: adjusted exceeds one[0.076923, 1.0][0.076923, 1.0]Passed
control: immediate deaths[0.093333, 0.093333][0.093333, 0.093333]Passed
control: single day[0.025, 0.041667][0.025, 0.041667]Passed

SHA-256 / 76d1168c4a9e923c2a14b9d47228031621ac201a371e7a96e95cf112f2f023f8

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 = [[('control: 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: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667])],
 [('control: 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: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: late surge',
   ([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
   [0.024242, 0.111111])],
 [('control: 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: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: late surge',
   ([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
   [0.024242, 0.111111])],
 [('control: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: 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),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: late surge',
   ([5, 5, 5, 60, 90], [0, 0, 1, 1, 2], [0.05, 0.25, 0.4, 0.3]),
   [0.024242, 0.111111])],
 [('control: 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: stable epidemic',
   ([50, 50, 50, 50, 50, 50], [0, 1, 2, 2, 2, 2], [0.2, 0.5, 0.3]),
   [0.03, 0.036735]),
  ('regression: long delay no outcomes', ([5, 8, 12], [0, 0, 0], [0, 0, 0, 0, 0, 1]), [0.0, None]),
  ('control: adjusted exceeds one', ([1, 1, 50], [1, 1, 2], [0.0, 0.0, 1.0]), [0.076923, 1.0]),
  ('control: immediate deaths', ([20, 30, 25], [2, 3, 2], [1.0]), [0.093333, 0.093333]),
  ('control: single day', ([40], [1], [0.6, 0.4]), [0.025, 0.041667]),
  ('control: 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 fixtureActualExpectedOutcome
control: growing epidemic[0.025806, 0.072727][0.025806, 0.072727]Passed
control: stable epidemic[0.03, 0.036735][0.03, 0.036735]Passed
regression: long delay no outcomes[0.0, None][0.0, None]Passed
control: no casesNoneNonePassed
control: adjusted exceeds one[0.076923, 1.0][0.076923, 1.0]Passed
control: immediate deaths[0.093333, 0.093333][0.093333, 0.093333]Passed
control: single day[0.025, 0.041667][0.025, 0.041667]Passed

SHA-256 / 68046d9a35202705b11dae6e34562c30cfd4b71a0cde954f04382641e846665f

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.121504+00:00.

Case digest / 823a0b54cf5d89ca39477823db1efcb830758c8bbb25a9993783f934c90da62e