Typhoid SEIRC — a spatial agent-based model with a small chronic-carrier compartment (~3% of survivors) that sustains endemic typhoid alongside acute transmission, plus a spatial sanitation intervention targeted at carrier-mediated transmission (urban patches only). Historical parallel: early 20th-century US water treatment infrastructure was urban-first, leaving rural typhoid endemic for decades longer.
Status: prototype; not calibrated to any real-world dataset. Parameters were chosen from published typhoid literature (Pitzer 2014, Antillón 2017, Ames & Robins 1943), then subjected to a 1D per-parameter sensitivity sweep — see
sensitivity/SENSITIVITY.md.
A small chronic-carrier compartment (~3% of survivors) sustains endemic typhoid alongside acute transmission. Water & food sanitation targeted at carrier-mediated transmission produces a substantial but not-complete reduction in incidence.
Compartments and flows:
S -> E -> I -> R (97% of acute cases; immunity wanes over ~6 yr)
I -> C (3% become chronic biliary carriers, shedding for decades)
R -> S (waning immunity)
C -> R (rare natural clearance)
Force of infection carries a reduced-weight carrier contribution:
λ(t) = β · (I(t) + α(t) · C(t)) / N(t)
where α is the shedding intensity ratio — carriers shed roughly 5% of what acute cases do. The sanitation intervention drops α from ALPHA_BASELINE to ALPHA_AFTER at year SANITATION_START_YEAR in the urban core + ring only (13 patches out of 25). Rural patches keep α_baseline.
Real-geography demonstration on Fiji's 5 admin-1 divisions (Central, Western, Northern, Eastern, Rotuma). Boundaries come from GADM and populations from WorldPop 2020 via the laser-init microservices; the cached responses ship under laser_typhoid/data/fiji/ so the runner works fully offline.
Sanitation intervention is applied at year 30 to Central and Western only — the two urban divisions that together carry ~82% of Fiji's population. Northern, Eastern, and Rotuma keep the baseline shedding intensity, mirroring the urban-first sewage rollout pattern.
Six panels:
- A — Aggregate S / R / C compartments; carrier stock grows through year 30, then rolls over as the urban intervention starves the transmission cycle in Central + Western.
- B — Urban (Central + Western) vs rural (rest) active-I burden. Urban drops sharply at intervention; rural continues largely on its previous trajectory.
- C — Per-division daily incidence (14-day smoothed, log scale). All five divisions on one chart makes the urban / rural gap immediate.
- D — Choropleth of cumulative infections per capita at year 30 (pre-intervention). Central and Western are darker.
- E — Same choropleth at year 50 (post-intervention). Central and Western fade while Northern / Eastern continue to accumulate.
- F — Chronic-carrier stock at end, per division. Central still holds the largest reservoir in absolute terms but the rate has flattened; rural divisions keep accumulating.
Animations:
Same model on a synthetic 5×5 grid. Useful for isolating what the intervention design does without the confound of real-geography population imbalances (Fiji's Central and Western together are 82% of the country; the synthetic grid has more uniform patches).
pip install .[geo] # Fiji runner needs geopandas + shapely + requests
python -c "from laser_typhoid.run_fiji import run_fiji; r = run_fiji({'YEARS': 35}, verbose=True); print(r['scenario'].name.values)"Or as scripts:
laser-typhoid-fiji --years 50 --out out_fiji.png # real Fiji geography
laser-typhoid --years 35 --seed 42 # synthetic 5×5 gridSynthetic-grid model (base install; no geo deps needed):
from laser_typhoid import DEFAULTS, run_model, metrics
result = run_model(overrides={"P_CARRIER": 0.05, "YEARS": 40}, verbose=True)
model = result["model"] # laser-generic Model instance
urban_nids = result["urban_nids"] # set of patch ids that see sanitation
print(metrics(result))Real-geography Fiji runner (requires [geo] extras):
from laser_typhoid.run_fiji import DEFAULTS as FIJI_DEFAULTS, run_fiji
from laser_typhoid.plots_fiji import save_summary_figure
result = run_fiji({"YEARS": 50}, verbose=True)
scenario = result["scenario"] # GeoDataFrame with 5 divisions
urban_mask = result["urban_mask"] # boolean array over divisions
save_summary_figure(result, "out_typhoid_fiji.png")Unknown keys in overrides raise KeyError early. metrics() extracts six outcome scalars used by the sensitivity harness: chronic-carrier fraction at year 30 (pre-sanitation), endemic active-typhoid prevalence at year 30, urban and rural 5-year-pre-vs-5-year-post incidence reductions, chronic-carrier fraction at end of sim, and final total population.
From published typhoid literature; not calibrated to any specific dataset. See sensitivity/SENSITIVITY.md for the per-parameter perturbation study.
| symbol | value | source / justification |
|---|---|---|
| R_ACUTE | 2.0 | Pitzer et al 2014 |
| P_CARRIER | 0.03 | Ames & Robins 1943 (25-yr Michigan cohort) |
| EXP_SCALE_DAYS | 12 d | E period (incubation); Antillón 2017 (8-14 d range) |
| INF_SCALE_DAYS | 18 d | I period; untreated acute typhoid |
| CARRIER_PERSIST_SCALE_DAYS | 50 yr | C→R natural clearance; near-lifelong biliary carriage |
| WANING_SCALE_DAYS | 6 yr | R→S immunity waning; Antillón 2017 (~15%/yr loss) |
| ALPHA_BASELINE | 0.05 | carrier shedding intensity ratio; Pitzer et al 2014 |
| ALPHA_AFTER | 0.005 | urban post-sanitation shedding intensity (10× reduction) |
| SANITATION_START_YEAR | 30 | when sanitation kicks in |
| CBR | 30/1000/yr | typical low/middle-income |
| CDR | 15/1000/yr | net +1.5%/yr population growth |
| GRID_M × GRID_N | 5×5 = 25 patches | urban core (60k), ring (20k each), rural periphery (8k each). Urban core+ring = 13 patches get the sanitation intervention. |
| YEARS | 50 | to see endemic dynamics + post-intervention response |
| SEED | 20260710 |
A 1D per-parameter perturbation study (±30% around each science parameter, three stochastic seeds per cell, 35-year sims — long enough to include the SANITATION_START_YEAR + 5 years post) landed alongside this repo — see sensitivity/SENSITIVITY.md for the tornado charts and per-parameter interpretation. Rerun with:
python -m sensitivity.sensitivity --years 35 --seeds 3 --workers 1
python -m sensitivity.make_tornado- Standard SIR-with-carrier models often exaggerate the carrier contribution because they omit birth/waning refilling of
S. Adding waning immunity puts carriers in the right place: important but not dominant. - Historical parallel: in the U.S. between 1900 and 1950, typhoid deaths fell from ~30/100 000/yr to <1, primarily through progressive water-treatment infrastructure and food-safety regulation targeted at carrier-mediated routes. The model's simulated incidence drop from a single urban sanitation step is consistent with the shape of that historical curve (not the magnitude — this isn't calibrated).
- Spatial urban-first rollout is realistic: real early sewage systems reached cities decades before rural areas. The rural-continues-endemic-while-urban-drops asymmetry is what the model reproduces.
- The "Typhoid Mary" archetype is quantitative here:
P_CARRIER = 0.03means ~3% of acute cases become chronic; each carrier persists ~50 years on average. That's the individual-level version of the Mary Mallon story.
- Vaccination: modern typhoid conjugate vaccines (TCV) have ~85% efficacy for ~3 yr. Model as periodic routine immunization creating a
Vcompartment. - Age structure: young children have higher carrier acquisition rates in some settings (Levine 1982). Add age-dependent
P_CARRIER. - Antibiotic treatment: acute
I → Rhappens faster with treatment (mean 5 d) but carrier prevalence unchanged. Split I into "treated" and "untreated" branches. - Water compartment: an explicit environmental reservoir with its own decay dynamics (cf. Codeço 2001 cholera model).
- More country runners: the Fiji runner is a template. Adding e.g. Bangladesh (BGD) or Malawi (MWI) at admin-1 is a
_laser_init.load_real_scenario("BGD", level=1, year=2020)call plus settingURBAN_DIVISIONSfor that country.
- Pitzer VE et al. 2014. Predicting the impact of vaccination on the transmission dynamics of typhoid in South Asia. Am J Epidemiol 179:1188-1197.
- Antillón M et al. 2017. The burden of typhoid fever in low- and middle-income countries. PLoS Negl Trop Dis 11:e0005376.
- Levine MM et al. 1982. Precise estimation of the numbers of chronic carriers of Salmonella typhi in Santiago, Chile. J Infect Dis 146:724-726.
- Ames WR, Robins M. 1943. Age and sex as factors in the development of the typhoid carrier state. AJPH 33:221.
- Bhan MK et al. 2005. Typhoid and paratyphoid fever. Lancet 366:749-762.
CARRIER = np.int8(5)extends the laser-genericStateenum. Model is instantiated withadditional_states=["C"]so_initialize_flowspropagatesC[t] → C[t+1].- Carrier fate is drawn at
E → Itransition time (insideExposedTyphoid.step) and stored inpeople.fate. ThenInfectiousTyphoid.stepuses two fated countdown-transitions to send agents to R or C onitimerexpiry. - Waning immunity uses
people.rtimer(uint32) sampledExponential(scale)onI → R.WaningImmunity.step()decrements and sendsR → Son expiry. ChronicCarrieralso decrementsctimer(uint32) for slowC → Rclearance.- Only agents whose
rtimer > 0are subject to waning — this preserves the invariant that agents in R via carrier clearance stay permanently R. - The mortality mappings passed to
MortalityByCDRinclude theCARRIERstate sonodes.Cis decremented on death.

