FAILURE MAP
← Case archive

FA-64956 / Epidemic compartment models / Open access

SIRS waning-immunity simulation: permanent immunity sentinel · case 01

Declaring permanent immunity (0) makes every recovered person susceptible again the next day.

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

ROOT CAUSE

The 0 sentinel falls through to instantaneous waning of the whole R class.

VERIFIED REPAIR

Restore the permanent immunity sentinel rule: `if immunity_days > 0 else 0.0`.

Unsuccessful approach: Substituting a one-year duration still wanes immunity that should be permanent.

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 r
        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 = [[('control: 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: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744])],
 [('control: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('control: 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: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: 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]),
  ('control: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('control: 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: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: 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
control: seasonal coronavirus[543.377, 58.225, 398.398, 1339.789][543.377, 58.225, 398.398, 1339.789]Passed
regression: permanent immunity sentinel[200.02, 249.985, 49.995, 1528.612][48.675, 7.802, 443.523, 446.325]Failed
control: 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
control: no transmission waning only[98.35, 0.04, 1.61, 0.0][98.35, 0.04, 1.61, 0.0]Passed
control: long immunity single wave[376.102, 3.345, 1620.554, 2084.424][376.102, 3.345, 1620.554, 2084.424]Passed
control: one day immunity[188.175, 86.35, 25.475, 420.744][188.175, 86.35, 25.475, 420.744]Passed

SHA-256 / f3e858b9d0498694abfd2f3c70fd688ecc557eec4ccbeeb6b204e4a6799a5935

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 r / 365.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 = [[('control: 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: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744])],
 [('control: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('control: 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: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: 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]),
  ('control: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('control: 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: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: 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
control: seasonal coronavirus[543.377, 58.225, 398.398, 1339.789][543.377, 58.225, 398.398, 1339.789]Passed
regression: permanent immunity sentinel[63.594, 9.219, 427.187, 454.955][48.675, 7.802, 443.523, 446.325]Failed
control: 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
control: no transmission waning only[98.35, 0.04, 1.61, 0.0][98.35, 0.04, 1.61, 0.0]Passed
control: long immunity single wave[376.102, 3.345, 1620.554, 2084.424][376.102, 3.345, 1620.554, 2084.424]Passed
control: one day immunity[188.175, 86.35, 25.475, 420.744][188.175, 86.35, 25.475, 420.744]Passed

SHA-256 / d35db3fec2220d8c4a7f3b1e477d9bb6ee5b10d9ef6e7df642cabc14b372c029

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 = [[('control: 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: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744])],
 [('control: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('control: 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: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: 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]),
  ('control: 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]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: two week run', (0.9, 0.3, 20, 400, 4, 14), [58.25, 94.786, 246.964, 386.248])],
 [('control: 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: short immunity endemic', (0.6, 0.25, 10, 800, 8, 100), [333.272, 133.331, 333.396, 3385.33]),
  ('control: no transmission waning only', (0.0, 0.3, 5, 100, 50, 20), [98.35, 0.04, 1.61, 0.0]),
  ('control: long immunity single wave',
   (0.5, 0.1, 365, 2000, 10, 120),
   [376.102, 3.345, 1620.554, 2084.424]),
  ('control: one day immunity', (0.5, 0.3, 1, 300, 3, 30), [188.175, 86.35, 25.475, 420.744]),
  ('control: 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
control: 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
control: 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
control: no transmission waning only[98.35, 0.04, 1.61, 0.0][98.35, 0.04, 1.61, 0.0]Passed
control: long immunity single wave[376.102, 3.345, 1620.554, 2084.424][376.102, 3.345, 1620.554, 2084.424]Passed
control: one day immunity[188.175, 86.35, 25.475, 420.744][188.175, 86.35, 25.475, 420.744]Passed

SHA-256 / 79733020054082ce39dc0324b71a156ec4ed0b8268dd397c9928a080d3eef2a9

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

Case digest / d112dce795b672b9a4c2720c1ef1863059e88181d1ba74a059526feca7db7524