Skip to content

Releases: epistorm/epydemix-webapi

v0.11.0

Choose a tag to compare

@mu373 mu373 released this 23 Oct 23:34
2f4f4ce

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

Choose a tag to compare

@mu373 mu373 released this 21 Aug 21:48
2c614c2

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

Choose a tag to compare

@mu373 mu373 released this 28 May 03:39

v0.9.0

Overview

  • Add fixed_rate vaccination rollout
  • Add an optional coverage cap 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

Choose a tag to compare

@mu373 mu373 released this 26 May 22:43

v0.8.0

Overview

  • Add the V-SEIR model preset
  • V-SEIHR default initial conditions now seed Exposed and Infected evenly (~0.025% each) instead of putting the full 0.05% seed in Infected.
  • Fix a partial-last-day aggregation issue when simulation.dt < 1: the daily-resampled transition sum on the user-requested end_date was being under-counted by a factor of dt (e.g. half on the last day with dt=0.5). The service now internally pads end_date by 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

v0.7.2

Choose a tag to compare

@mu373 mu373 released this 20 May 21:46

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

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

Choose a tag to compare

@mu373 mu373 released this 19 May 21:46

v0.7.1

Overview

  • New interactive /playground page 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
  • 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) and absolute methods, when to pick each, and what the fallback behavior is for omitted blocks
    • Custom Models example now includes an explicit initial_conditions block and notes the lack of a built-in default for custom presets
  • All model preset pages document R0 and 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

Choose a tag to compare

@mu373 mu373 released this 19 May 00:13

v0.7.0

Overview

  • Add the V-SEIHR model preset: Susceptible-Exposed-Infected-Hospitalized-Recovered with a parallel vaccinated branch and two efficacy parameters (VE_S against susceptibility, VE_H against hospitalization)
  • Add a top-level vaccination block to POST /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:

$$ \lambda^{\text{vax}}_i(t) = (1 - \mathrm{VE_S}) \cdot \lambda_i(t), \qquad p_H^{\text{vax}} = (1 - \mathrm{VE_H}) \cdot p_H. $$

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 $d_c(t)$ into a per-individual rate by spreading those doses across the live eligible pool:

$$ r_c(t) = \frac{d_c(t)}{\displaystyle\sum_{X \in \mathcal{S}};\sum_{j \in \mathcal{A}_c} X_j(t)}. $$

Highlights of the rollout layer:

  • Campaigns carry their own start_date, end_date, optional name, and optional target_age_groups (a subset of the resolved population's labels; null means all groups).
  • Rollout strategies are discriminated by rollout.type. v1 ships flat_count (a constant daily_doses budget held across the window, distributed proportionally to the source pool per age group).
  • Multiple campaigns stack: overlapping flat_count campaigns 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-SEIHR the default is Susceptible → 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

Choose a tag to compare

@mu373 mu373 released this 13 May 16:35

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_ALL usable 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 $R_0$:

{
  "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_parameter must be a source parameter (scalar or age-varying), not a calculated one
  • The Populations Overview R0-calibration section now points at CONTACT_MATRIX_EIGENVALUE_ALL as 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

Choose a tag to compare

@mu373 mu373 released this 04 May 21:34

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 $R_0$.

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

Choose a tag to compare

@mu373 mu373 released this 04 May 20:06

Overview

  • Add custom populations to POST /api/v1/simulations: define age_groups and contact_matrices directly in the request body
  • metadata.population in the response now carries source and contact_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=md query on any docs page returns the markdown source instead of the rendered HTML
  • /llms-full.txt now bundles the OpenAPI JSON spec alongside the doc-page content