Repository navigation
Releases: epistorm/epydemix-webapi
Release list
v0.11.0
v0.11.0
Overview
v0.11.0 addresses the simulation counting, preset defaults, and input handling
reported in epydemix issue #25.
Existing clients need to account for changed defaults and HTTP error responses.
Correct infection counts
The API now requires epydemix 1.4.0 or later, with 1.4.0 recorded in uv.lock.
This includes the upstream fix for duplicate transition counts when multiple
mediated transitions share the same source and target compartments.
V-SEIR and V-SEIHR now count each infection once. Custom models with the same
transition pattern also receive the fix.
Preset defaults
SIR, SEIR, and SIS now default to R0: 2.5 instead of a fixed
transmission_rate: 0.3. The API derives the transmission rate from the recovery
rate and the selected population's contact matrix. The default recovery rate
remains 0.1.
The previous fixed rates could imply a much larger R0 depending on the
population's contact matrix. For example, the effective R0 for Vermont was
approximately 42.
Requests that omit both R0 and transmission_rate produce different results.
To retain the previous transmission rate, pass "transmission_rate": 0.3
explicitly. V-SEIR and V-SEIHR already default to R0: 2.5.
See Preset conversions and precedence.
Input validation and HTTP errors
Invalid requests now return HTTP 422 before the simulation starts. Validation
covers invalid or reversed dates, unknown initial-condition compartments,
out-of-range or non-finite numeric inputs, invalid model references, and negative
resolved transition rates. Python exports also validate their inputs.
Simulation failures no longer return HTTP 200 with "status": "failed".
Clients must handle HTTP errors and read the response's detail field:
| HTTP status | Meaning |
|---|---|
422 |
Invalid request |
504 |
Built-in population loading timed out |
500 |
Unexpected internal error, with details kept in server logs |
Successful simulations still return HTTP 200 with "status": "completed".
See Errors for the request constraints.
Parameter warnings
Simulation responses now include a warnings list that explains when an input
was ignored. For example, passing both R0 and transmission_rate uses
transmission_rate and returns a parameter_overridden warning for R0.
The simulation still runs using the explicit rate. This also applies when an
explicit rate expression does not use the source, directly or through another
expression.
There is no warning when only a preset default is overridden or when the rate
expression uses the supplied source. See Warnings for
examples and the warning fields.
Documentation
- Update preset and parameter guides with the new defaults and precedence rules.
- Document input constraints, HTTP errors, and parameter warnings.
- Regenerate the OpenAPI reference for v0.11.0.
v0.10.0
v0.10.0
Overview
Adds Python export endpoints that return executable Python code equivalent to requests sent to the web API. The generated scripts call epydemix directly and can be inspected and run locally.
Python code export
- Export complete simulation requests.
- Export population listings, custom populations, named populations, and contact matrices.
- Export model preset construction.
- Preserve calculated parameters, R0 conversion, Balcan seasonality, scaling, overrides, interventions, vaccination campaigns, initial conditions, output quantiles, and subdaily simulation settings.
- Write compartment and transition quantiles to CSV from exported simulation scripts.
Documentation
v0.9.0
v0.9.0
Overview
- Add
fixed_ratevaccination rollout - Add an optional
coveragecap to each vaccination campaign
Simulations
POST /api/v1/simulations
New rollout strategy: fixed_rate
Each campaign's rollout block now accepts a new type fixed_rate, in addition to flat_count:
"rollout": {
"type": "fixed_rate",
"rate": 0.005
}Inside the campaign window this is identical to a spontaneous transition at rate: every individual in a source compartment (restricted to target_age_groups) has a per-day probability of 1 - exp(-rate * dt) of moving to the target.
See the Campaigns guide for the worked example and the fixed_rate playground template for a V-SEIHR setup.
New per-campaign coverage cap
Each campaign now accepts an optional coverage block that stops the campaign once cumulative coverage reaches a target fraction of the initial population in target_age_groups:
"coverage": {
"fraction": 0.7,
"compartments": ["Susceptible_vax", "Exposed_vax", "Infected_vax", "Recovered_vax"]
}Once the summed current occupancy of compartments, restricted to the campaign's target_age_groups, reaches fraction * initial_population, the campaign stops contributing to the rate. The cap composes with both flat_count and fixed_rate, and is independent per campaign.
See the Coverage cap section of the Campaigns guide for details and validation rules.
Playground
- New V-SEIHR (fixed-rate vaccination) template.
Documentation
- New "Fixed rate (per-day hazard)" and "Coverage cap" sections in the Campaigns guide.
v0.8.0
v0.8.0
Overview
- Add the
V-SEIRmodel preset - V-SEIHR default initial conditions now seed
ExposedandInfectedevenly (~0.025%each) instead of putting the full0.05%seed inInfected. - Fix a partial-last-day aggregation issue when
simulation.dt < 1: the daily-resampled transition sum on the user-requestedend_datewas being under-counted by a factor ofdt(e.g. half on the last day withdt=0.5). The service now internally padsend_dateby one calendar day and trims the trailing day before responding.
Simulations
POST /api/v1/simulations
New model preset: V-SEIR
V-SEIR mirrors V-SEIHR minus the hospitalization branch, with the same VE_S semantics on the vaccinated branch. The vaccination block from v0.7.0 works unchanged.
See the V-SEIR preset page for compartments, transitions, parameters, and examples.
V-SEIHR default initial conditions
The default initial-condition seed for V-SEIHR (used when the request omits an initial_conditions block) was changed from Infected = 0.05%, Susceptible = 99.95% to Exposed = 0.025%, Infected = 0.025%, Susceptible = 99.95%. Because the previous seed had Exposed=0 at t=0, the first E -> I transition could only fire after a full incubation period of build-up, producing a visible discontinuity at step 1 in incidence plots.
Requests that supply an explicit initial_conditions block are unaffected.
Last-day aggregation with sub-daily dt
When simulation.dt < 1.0 and the output is resampled to daily (resample_frequency="D", the default), the user-requested end_date previously received only one sub-step instead of 1/dt. For example, with dt=0.25 the last day's Exposed_to_Infected count would be roughly a quarter of what it should be.
We have modified the internal simulation logic to ensure that the last day collects the full 1/dt sub-steps when dt < 1.0. The user-facing response shape is unchanged: metadata.simulation.end_date still reports the requested date, and the dates axis ends on it.
Documentation
- New V-SEIR preset page
- Fixed a broken link in the playground page
v0.7.2
v0.7.2
Overview
- Faster simulations (~2.7x) with identical output, via an engine upgrade and population caching
- Upgraded epydemix to v1.2.1
- Built-in population data is now cached across requests
- Deployment: Python 3.13 image, configurable
PORT, Cloud Run guide
Performance
Simulations are significantly faster, with identical output for a given request and seed. Measured on Google Cloud Run, a year-long V-SEIHR + vaccination run (US population, Nsim=10) completes in ~3.6s end-to-end, about 2.7x faster than v0.7.1. Speedup scales with simulation horizon, compartment count, and Nsim.
Deployment
- The container honors the
PORTenvironment variable (default8000). - Base image and project Python bumped to 3.13.
- New Google Cloud Run deployment guide.
Internals
- Upgraded epydemix to v1.2.1, which resolves plain parameter references directly instead of copying the full parameter set each time step (the case our presets hit).
- Built-in populations (e.g.
United_States) and contact matrices are now cached in-process instead of re-downloaded on every request. Custom inline populations are unaffected.
v0.7.1
v0.7.1
Overview
- New interactive
/playgroundpage with a request editor and response viewer with plots - V-SEIHR now ships a default initial condition
- Bug fixes in V-SEIHR response metadata
- Peak summaries no longer leak
vax_*keys into unvaccinated compartments age groups - Vaccination flows are now echoed back in the response metadata
- Peak summaries no longer leak
- Documentation improvements: initial conditions page, parameterization options, UI refinements
- Expanded test coverage: SIR dynamics (R0 monotonicity, subcritical dieout, ODE match, seasonality suppression), value-level assertions on parameter transforms, and a new unit-test suite for initial conditions
Simulations
POST /api/v1/simulations
V-SEIHR default initial conditions
If you omit initial_conditions on a V-SEIHR request, the API now seeds ~0.05% of each age group as Infected and puts the rest in Susceptible. The _vax branch starts at zero. See the new Initial Conditions page.
Resolved vaccination flows in metadata
POST /simulations with a V-SEIHR request and a vaccination block but no explicit vaccination.flows now echoes the resolved default flow back in the response:
"metadata": {
"vaccination": {
"flows": [{ "source": "Susceptible", "target": "Susceptible_vax" }],
"campaigns": [ /* ... */ ]
}
}Previously flows stayed null in the response even though the simulator had defaulted it internally.
Summary peaks no longer leak _vax keys
For V-SEIHR responses, results.summary.peaks.Susceptible used to contain age-group-shaped entries like vax_0-4, vax_total, etc. This bug has been fixed so that only age groups corresponding to the unvaccinated branch are included in the summary peaks.
Playground
A new page for trying simulations interactively is now available at /playground:
- Request JSON editor: Write the request body directly in JSON or pick from one of the provided templates
- Response viewer: After you run the simulation, you can see selected compartments/transitions/parameters plotted over time, with the full JSON response available in a separate tab.
Documentation
- Added information about initial conditions
- New Initial Conditions page covering the
percentage(default) andabsolutemethods, when to pick each, and what the fallback behavior is for omitted blocks - Custom Models example now includes an explicit
initial_conditionsblock and notes the lack of a built-in default for custom presets
- New Initial Conditions page covering the
- All model preset pages document
R0and period-form parameters as alternatives to the rate forms (SIR, SEIR, SIS). - Documentation refinement: the sidebar can be collapsed with a toggle button; frontend dependency updates
v0.7.0
v0.7.0
Overview
- Add the
V-SEIHRmodel preset: Susceptible-Exposed-Infected-Hospitalized-Recovered with a parallel vaccinated branch and two efficacy parameters (VE_Sagainst susceptibility,VE_Hagainst hospitalization) - Add a top-level
vaccinationblock toPOST /simulations: declarative campaigns that turn a daily dose budget into a per-individual vaccination rate, with age-group targeting, multiple overlapping campaigns, dose sinks, and multi-target flows - Refactor model presets into a per-file registry: each preset module owns its compartments, transitions, parameter defaults, and parameter conversions
Simulations
POST /api/v1/simulations
New preset: V-SEIHR
The new V-SEIHR preset extends SEIHR with a vaccinated twin of every compartment (Susceptible_vax, Exposed_vax, Infected_vax, Hospitalized_vax, Recovered_vax). Vaccinated susceptibles see an attenuated force of infection and vaccinated infected hospitalize at a reduced rate:
Vaccinated infectious individuals transmit at the same per-contact rate as unvaccinated; the reduction sits on susceptibility, not transmissibility.
Defaults: R0 = 2.5, incubation_period = 3.0, infectious_period = 2.5, hosp_duration = 5.0, VE_S = 0.7, VE_H = 0.85, and an age-stratified hosp_proportion = [0.002, 0.005, 0.015, 0.05, 0.18] matching the five-bin built-in population. Pass a scalar for homogeneous behavior, or a length-N list to match a population with a different number of age groups.
Without a vaccination block the vaccinated branch stays at zero for the whole run and V-SEIHR behaves like plain SEIHR.
See the V-SEIHR preset page for the full transition table, ODE form, parameter reference, and curl examples.
Vaccination block
A new top-level vaccination object drives doses through the model:
{
"model": {
"preset": "V-SEIHR",
"parameters": {"R0": 2.5, "VE_S": 0.7, "VE_H": 0.85}
},
"population": {"name": "United_States"},
"simulation": {"start_date": "2025-01-01", "end_date": "2025-06-30", "Nsim": 10},
"vaccination": {
"campaigns": [
{
"start_date": "2025-02-01",
"end_date": "2025-04-30",
"target_age_groups": ["65+"],
"rollout": {"type": "flat_count", "daily_doses": 100000}
}
]
}
}Each campaign turns a daily dose budget
Highlights of the rollout layer:
- Campaigns carry their own
start_date,end_date, optionalname, and optionaltarget_age_groups(a subset of the resolved population's labels;nullmeans all groups). - Rollout strategies are discriminated by
rollout.type. v1 shipsflat_count(a constantdaily_dosesbudget held across the window, distributed proportionally to the source pool per age group). - Multiple campaigns stack: overlapping
flat_countcampaigns add their daily doses at every step. Useful for booster waves or for opening eligibility on a given date by adding a younger-age campaign mid-rollout. - Flows declare which compartments compete for doses and where vaccinated individuals move. On
V-SEIHRthe default isSusceptible → Susceptible_vax; custom models must declare flows explicitly. - Dose sinks are flows with
target: null: the source contributes to the rate denominator but no transition fires. Models a real-world campaign that cannot pre-screen recipients (e.g. doses landing on already-recovered people are still consumed). - Multi-target flows share one budget across two susceptible strata. Each source draws from the shared budget in proportion to its live size, and each pair emits its own
<source>_to_<target>transition at the same per-individual rate.
Each transitioning flow surfaces as a regular transition in the response (e.g. Susceptible_to_Susceptible_vax under results.transitions.data); flows with dose sinks emit no transition. The request is echoed back under metadata.vaccination with the resolved flows list.
See the Vaccination overview, Campaigns page, and the FAQ entry on disabling vaccination for the full details and custom-model usage.
Per-preset registry and parameter conversions
Predefined models (SIR, SEIR, SIS, V-SEIHR) have been refactored into a per-file preset registry. Each preset module now owns its own compartments, transitions, parameter defaults, and parameter conversions (e.g. infectious_period → recovery_rate, hosp_duration → hosp_recovery_rate). Conversions used to live in a single central dictionary; they now live alongside the preset that uses them, which makes it easier to add new presets without touching shared code.
Behavior on overlap is unchanged: if both the rate form and its period form are sent, the rate wins and the source is dropped silently.
Parameter rename
In the V-SEIHR preset, the hospital-stay duration parameter was renamed from hospitalization_period to hosp_duration for consistency with hosp_proportion and hosp_recovery_rate. The corresponding rate is hosp_recovery_rate (default: 1 / hosp_duration).
Documentation
- New Vaccination guide category with Overview (rate calculation, flows, dose allocation, vaccine efficacy, inspecting the rollout) and Campaigns (per-campaign fields, rollout strategies, multi-campaign stacking, custom-model usage)
- New V-SEIHR preset page with compartments, transitions, equivalent ODE form, vaccine-efficacy math, parameter table, and three curl examples (with vaccination, without vaccination, with seasonality)
- Model presets split into per-preset pages under
guides/model/presets/(SIR, SEIR, SIS, V-SEIHR) - Parameter docs grouped under
guides/model/parameters/with an Overview and a dedicated Transformations page - FAQ restructured under Models / Parameters / Vaccination and gained three new entries: period-vs-rate,
$R_0$ -vs-transmission_rate, and disabling vaccination - Algolia DocSearch wired up via env vars loaded from
.env.local
v0.6.0
Overview
- Add calculated parameters to
model.parameters: a string value is treated as an arithmetic expression over other parameters and evaluated at simulation build time - Expose the largest eigenvalue of the contact matrix as a reserved parameter
CONTACT_MATRIX_EIGENVALUE_ALLusable in calculated-parameter expressions - Regenerate population metadata to match the current upstream subnational naming (single-underscore → double-underscore)
Simulations
POST /api/v1/simulations
Calculated parameters
Values in model.parameters can now be expressions over the other parameters. Useful for branching outflow rates and for deriving one parameter from another:
{
"parameters": {
"p_h": 0.05,
"gamma": 0.1,
"alpha": 0.2,
"recovery_rate": "(1 - p_h) * gamma",
"hospitalization_rate": "p_h * alpha"
}
}Source-parameter shapes propagate via numpy broadcasting, so an age-varying or seasonally-scaled source flows through the expression automatically. Calculated parameters evaluate after parameter_transforms.
Expressions can only include strict arithmetic syntax: +, -, *, /, //, **, %, unary +/-, parentheses, numeric literals, and parameter names. No function calls, attribute access, subscripts, comparisons, or conditionals. Circular references and undefined names raise 422 with the offending name.
A calculated parameter cannot be the target_parameter of a parameter_transform. Apply the transform to a source parameter instead and let it propagate through the expression.
See the Parameters guide for the full description and examples.
Reserved name: CONTACT_MATRIX_EIGENVALUE_ALL
The largest eigenvalue of the summed contact matrix (across all layers) is available as CONTACT_MATRIX_EIGENVALUE_ALL. The most common use is calibrating transmission_rate from a target
{
"parameters": {
"R0": 1.5,
"gamma": 0.1,
"recovery_rate": "gamma",
"transmission_rate": "R0 * gamma / CONTACT_MATRIX_EIGENVALUE_ALL"
}
}User parameter names cannot collide with reserved names. Reserved values are injected only into the expression namespace; they do not appear in results.parameters.
Populations
GET /api/v1/populations
Population name identifier update
A recent epydemix-data v1.2.0 update switched subnational identifiers from a single-underscore to a double-underscore separator (e.g. United_States__Massachusetts rather than United_States_Massachusetts). The precomputed population metadata CSVs (app/data/population_metadata.csv and app/data/population_age_distribution.csv) have been regenerated against the current release to match.
United States county-level populations
The same epydemix-data v1.2.0 update introduces ~3,100 US county-level populations (e.g. United_States__Massachusetts__Suffolk_County). Demographic distributions come from the US Census Bureau (2023); contact matrices are inherited from the corresponding state in the absence of county-specific survey data. See the Population Presets page for examples.
Documentation
- New Calculated parameters section in the Parameters guide: expression syntax, broadcasting/composition with transforms, reserved names, and an R0-calibration example
- Note in the Parameter Transforms guide that
target_parametermust be a source parameter (scalar or age-varying), not a calculated one - The Populations Overview R0-calibration section now points at
CONTACT_MATRIX_EIGENVALUE_ALLas the in-request shortcut - The Population Presets page reflects the new double-underscore convention across all subnational identifiers, and adds a section on US county-level populations (~3,100 counties) with examples
v0.5.1
Overview
- Detailed documentation on populations, including overview, presets, and customizations
- Dependency updates
Documentation
Populations
The populations guide is now a category with three pages: Overview, Population Presets, and Custom Populations. The overview covers age-stratified simulations, contact-matrix heterogeneity, and how to calibrate transmission_rate from a target
Running a Simulation
Added cross-links to the new Populations and Model pages, plus a jq one-liner for extracting the median peak of Infected.
Dependencies
- gitpython 3.1.46 → 3.1.49
- pillow 12.1.1 → 12.2.0
- pygments 2.19.2 → 2.20.0
- pytest 9.0.2 → 9.0.3
- python-dotenv 1.2.1 → 1.2.2
v0.5.0
Overview
- Add custom populations to
POST /api/v1/simulations: defineage_groupsandcontact_matricesdirectly in the request body metadata.populationin the response now carriessourceandcontact_matrices
Simulations
POST /api/v1/simulations
Custom populations
The population block now accepts a source discriminator. The default "builtin" keeps the existing data-repo path; "custom" lets you define everything inline:
{
"population": {
"source": "custom",
"name": "Custom Population 1",
"age_groups": {"A": 100000},
"contact_matrices": {"all": [[1.0]]}
}
}Existing payloads without a source field route to the builtin branch automatically:
{"population": {"name": "United_States"}}still validates and behaves exactly as before. No client changes required.
Documentation
- New OpenAPI request example "SIR in custom population (homogeneous)" exercises the
"custom"branch with a single-group, 1x1 contact matrix ?format=mdquery on any docs page returns the markdown source instead of the rendered HTML/llms-full.txtnow bundles the OpenAPI JSON spec alongside the doc-page content