FAILURE MAP
← Case archive

FA-64971 / Epidemic compartment models / Open access

SIRS waning-immunity simulation: cumulative incidence · case 01

Cumulative incidence stops growing, and even falls, once reinfections occur.

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

ROOT CAUSE

Cumulative incidence is inferred from susceptible depletion, which waning refills.

VERIFIED REPAIR

Restore the cumulative incidence rule: `cumulative += inf`.

Unsuccessful approach: Current I+R misses everyone whose immunity has already waned.

Case contract

Daily forward-Euler SIRS; waning flow is R/immunity_days back to S (immunity_days 0 means permanent immunity); cumulative incidence sums every S->I flow including reinfections; return [S, I, R, cumulative incidence] rounded to 3.

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(beta, gamma, immunity_days, pop, i0, days):
    s, i, r = float(pop - i0), float(i0), 0.0
    cumulative = 0.0
    for _ in range(days):
        inf = beta * s * i / pop
        rec = gamma * i
        wane = r / immunity_days if immunity_days > 0 else 0.0
        s += wane - inf
        i += inf - rec
        r += rec - wane
        cumulative = pop - s
    return [round(s, 3), round(i, 3), round(r, 3), round(cumulative, 3)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])]]
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
regression: seasonal coronavirus[543.377, 58.225, 398.398, 456.623][543.377, 58.225, 398.398, 1339.789]Failed
regression: permanent immunity sentinel[48.675, 7.802, 443.523, 451.325][48.675, 7.802, 443.523, 446.325]Failed
regression: short immunity endemic[333.272, 133.331, 333.396, 466.728][333.272, 133.331, 333.396, 3385.33]Failed
control: boundary zero days[95.0, 5.0, 0.0, 0.0][95.0, 5.0, 0.0, 0.0]Passed
regression: no transmission waning only[98.35, 0.04, 1.61, 1.65][98.35, 0.04, 1.61, 0.0]Failed
regression: long immunity single wave[376.102, 3.345, 1620.554, 1623.898][376.102, 3.345, 1620.554, 2084.424]Failed
regression: one day immunity[188.175, 86.35, 25.475, 111.825][188.175, 86.35, 25.475, 420.744]Failed

SHA-256 / 099c602af55f1761923c7e36d861d1fb281bbd7daec7f2e062babdb662ca4b14

2 / The unsuccessful fix

Exit 1
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(beta, gamma, immunity_days, pop, i0, days):
    s, i, r = float(pop - i0), float(i0), 0.0
    cumulative = 0.0
    for _ in range(days):
        inf = beta * s * i / pop
        rec = gamma * i
        wane = r / immunity_days if immunity_days > 0 else 0.0
        s += wane - inf
        i += inf - rec
        r += rec - wane
        cumulative = i + r
    return [round(s, 3), round(i, 3), round(r, 3), round(cumulative, 3)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])]]
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
regression: seasonal coronavirus[543.377, 58.225, 398.398, 456.623][543.377, 58.225, 398.398, 1339.789]Failed
regression: permanent immunity sentinel[48.675, 7.802, 443.523, 451.325][48.675, 7.802, 443.523, 446.325]Failed
regression: short immunity endemic[333.272, 133.331, 333.396, 466.728][333.272, 133.331, 333.396, 3385.33]Failed
control: boundary zero days[95.0, 5.0, 0.0, 0.0][95.0, 5.0, 0.0, 0.0]Passed
regression: no transmission waning only[98.35, 0.04, 1.61, 1.65][98.35, 0.04, 1.61, 0.0]Failed
regression: long immunity single wave[376.102, 3.345, 1620.554, 1623.898][376.102, 3.345, 1620.554, 2084.424]Failed
regression: one day immunity[188.175, 86.35, 25.475, 111.825][188.175, 86.35, 25.475, 420.744]Failed

SHA-256 / 9aa1bd985300a257c8e1515c673c6150aadc0bc95f904ec52d8cffd26a0b17f7

3 / The verified repair

Exit 0
"""Failure Map reference implementation. Python standard library only."""
import json
import math
N = 1
observations = []
def solve(beta, gamma, immunity_days, pop, i0, days):
    s, i, r = float(pop - i0), float(i0), 0.0
    cumulative = 0.0
    for _ in range(days):
        inf = beta * s * i / pop
        rec = gamma * i
        wane = r / immunity_days if immunity_days > 0 else 0.0
        s += wane - inf
        i += inf - rec
        r += rec - wane
        cumulative += inf
    return [round(s, 3), round(i, 3), round(r, 3), round(cumulative, 3)]
def check(label, actual, expected):
    observations.append({"check": label, "actual": actual, "expected": expected, "passed": actual == expected})
fixtures = [[('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('regression: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('regression: seasonal coronavirus', (0.4, 0.2, 30, 1000, 5, 80), [543.377, 58.225, 398.398, 1339.789]),
  ('regression: permanent immunity sentinel', (0.5, 0.2, 0, 500, 5, 40), [48.675, 7.802, 443.523, 446.325]),
  ('control: boundary zero days', (0.4, 0.2, 30, 100, 5, 0), [95.0, 5.0, 0.0, 0.0]),
  ('regression: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('regression: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('regression: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('regression: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])]]
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
regression: seasonal coronavirus[543.377, 58.225, 398.398, 1339.789][543.377, 58.225, 398.398, 1339.789]Passed
regression: permanent immunity sentinel[48.675, 7.802, 443.523, 446.325][48.675, 7.802, 443.523, 446.325]Passed
regression: short immunity endemic[333.272, 133.331, 333.396, 3385.33][333.272, 133.331, 333.396, 3385.33]Passed
control: boundary zero days[95.0, 5.0, 0.0, 0.0][95.0, 5.0, 0.0, 0.0]Passed
regression: no transmission waning only[98.35, 0.04, 1.61, 0.0][98.35, 0.04, 1.61, 0.0]Passed
regression: long immunity single wave[376.102, 3.345, 1620.554, 2084.424][376.102, 3.345, 1620.554, 2084.424]Passed
regression: one day immunity[188.175, 86.35, 25.475, 420.744][188.175, 86.35, 25.475, 420.744]Passed

SHA-256 / 55289b2c7cadb3a40b1730e77a858dd1f2456a1ef1d49416b15f79c673ac1343

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

Case digest / c04abf5b3ead168ccb3c78761e2f2aadce31ec24cc6231e5a2abf79246037a1f