Interventions
STIsim separates the product (what is being delivered) from the intervention (how it is delivered). A diagnostic product stores the characteristics of a test – its sensitivity, specificity, and what states it can detect. An intervention stores the delivery information – who is eligible, how often they are reached, and when the program starts and stops. This separation means you can swap out a diagnostic product without changing the delivery strategy, or target the same product to different populations.
Architecture
Intervention (delivery) Intervention (delivery)
- eligibility - eligibility
- test probability - treatment efficacy
- start/stop years - start/stop years
│ │
▼ ▼
Diagnostic product Disease module
(product characteristics) (clears infection)
- sensitivity by state
- specificity
Testing and treatment are separate interventions. A testing intervention uses a diagnostic product to determine outcomes, and can optionally trigger a treatment intervention for positive results. Alternatively, treatment can be applied directly (e.g., syndromic management without a lab test).
Testing
STITest
The base testing class. Controls who gets tested, how often, and with what diagnostic.
test = sti.STITest(
product=my_diagnostic, # Diagnostic product (required)
test_prob_data=0.1, # Annual testing rate (10% per year)
eligibility=lambda sim: sim.people.female, # Who is eligible
start=2015, # When testing begins
)Key parameters:
| Parameter | Default | Description |
|---|---|---|
product |
(required for STITest; HIVTest auto-creates a perfect HIVDx) | Diagnostic product (e.g., STIDx, HIVDx) |
test_prob_data |
1.0 | Annual testing probability (scalar or array over years). Converted to per-timestep probability via ss.probperyear when dt_scale=True. |
eligibility |
all agents (HIVTest: undiagnosed only) | Function f(sim) -> BoolArr defining who can be tested, e.g. lambda sim: sim.people.female & (sim.people.age >= 15) |
start / stop |
first/last sim year | Calendar year (inclusive) when the intervention activates/deactivates |
dt_scale |
True | If True (default), interpret test_prob_data as an annual probability. Set to False to interpret as a per-timestep probability. |
HIVTest
HIV-specific testing. Defaults to testing only undiagnosed agents and using a perfect diagnostic.
hiv_test = sti.HIVTest(test_prob_data=0.1, start=2000)On a positive result, sets sim.diseases.hiv.diagnosed = True.
SymptomaticTesting
Syndromic management: test symptomatic care-seekers and route them to treatment based on results. Used for bacterial STIs where lab confirmation may not be available.
syndromic = sti.SymptomaticTesting(
diseases=[ng, ct, tv],
treatments=[ng_tx, ct_tx, metro],
disease_treatment_map={'ng': ng_tx, 'ct': ct_tx, 'tv': metro},
)Treatment
STITreatment
The base treatment class. Clears infection for eligible agents with a given efficacy.
ct_tx = sti.STITreatment(
diseases='ct', # Which disease(s) to treat
treat_eff=0.95, # 95% efficacy
name='ct_tx',
)Key parameters:
| Parameter | Default | Description |
|---|---|---|
diseases |
(required) | Disease name or list of names to treat |
treat_prob |
1.0 | Probability of accepting treatment |
treat_eff |
0.9 | Treatment efficacy |
eligibility |
all agents | Function defining who can be treated |
Treatment outcomes are tracked as successful (infection cleared), failed (infection persists), or unnecessary (treated a susceptible agent).
Disease-specific treatments
| Class | Disease | Notes |
|---|---|---|
sti.STITreatment |
Any STI | Generic; specify disease(s) by name |
sti.GonorrheaTreatment |
Gonorrhea | Tracks antimicrobial resistance via rel_treat |
sti.SyphTx |
Syphilis | Treats pregnant mothers and fetuses; stage-specific |
sti.treat_BV |
BV | CST-based treatment with durable post-treatment effects |
HIV treatment pipeline
HIV treatment in STIsim follows a specific pipeline. Understanding this flow is essential for setting up HIV interventions correctly:
HIVTest ART
│ │
│ test eligible agents │ check newly diagnosed
│ (per-year probability) │ (ti_diagnosed == this step)
│ │
▼ ▼
hiv.diagnosed = True ──► art_initiation filter (90% default)
hiv.ti_diagnosed = ti │
▼
If coverage specified:
prioritize by CD4, match target
If no coverage:
treat all who initiate
│
▼
hiv.on_art = True
(reduces transmissibility, reconstitutes CD4)
Key points:
- Intervention order matters: interventions run in the order they appear in the list. HIVTest must come before ART so that agents diagnosed this timestep can initiate ART in the same step.
- Agents must be diagnosed before they can go on ART. If you add ART without HIVTest, a warning is raised and no agents will be treated.
test_prob_datais an annual testing probability by default (dt_scale=True). It is converted to a per-timestep probability viass.probperyear. With monthly timesteps,test_prob_data=0.1means ~0.88% per month, correctly recovering ~10% per year. To use a per-timestep probability directly, setdt_scale=False.- ART
art_initiation(default 0.9) controls what fraction of newly diagnosed agents are willing to start treatment. - If
coverageis provided, ART force-fits the number on treatment to match the target each timestep. Agents are added or removed immediately (not gradually) to hit the target, prioritized by CD4 count and care-seeking propensity. - If no
coverageis provided (the default), ART simply treats everyone who initiates, with no capacity constraint.
HIVTest
# Test 20% of undiagnosed agents per year
hiv_test = sti.HIVTest(test_prob_data=0.2, start=2000, name='hiv_test')
# Higher rate for FSWs
fsw_test = sti.HIVTest(
test_prob_data=0.5,
name='fsw_test',
eligibility=lambda sim: sim.networks.structuredsexual.fsw & ~sim.diseases.hiv.diagnosed,
)| Parameter | Default | Description |
|---|---|---|
test_prob_data |
1.0 | Annual testing probability (converted via ss.probperyear when dt_scale=True) |
eligibility |
undiagnosed | Function f(sim) -> BoolArr defining who can be tested |
start |
sim start | Year testing begins |
ART
ART accepts coverage in multiple formats:
# No coverage target — 90% of newly diagnosed initiate ART (default)
art = sti.ART()
# Treat ALL diagnosed, no coverage constraint
art = sti.ART(art_initiation=1)
# Constant proportion of infected
art = sti.ART(coverage=0.8)
# Time-varying proportion
art = sti.ART(coverage={'year': [2000, 2010, 2025], 'value': [0, 0.5, 0.9]})
# From a DataFrame (absolute numbers)
art = sti.ART(coverage=pd.read_csv('art_data.csv').set_index('year'))
# Historical absolute numbers then projected proportions (mixed n/p format)
df = pd.read_csv('n_art.csv').set_index('year')
df['p_art'] = np.nan
df.loc[2023:, 'p_art'] = 0.90
art = sti.ART(coverage=df)
# Age/sex stratified (columns: Year, Gender/Sex, AgeBin, + value)
art = sti.ART(coverage=stratified_df)
# Smoother interpolation (default 0 = linear)
art = sti.ART(coverage={'year': [2000, 2025], 'value': [0, 0.9]}, smoothness=5)| Parameter | Default | Description |
|---|---|---|
coverage |
None (no target; treat all who initiate) | Coverage target: scalar, dict, DataFrame, or stratified DataFrame. Dict values are interpolated between years (controlled by smoothness). DataFrame requires index=years and a column named n_art (absolute numbers) or p_art (proportion of infected). Dual-column DataFrames with both n_art and p_art support mixed format (use format_priority to resolve). Stratified DataFrames need columns Year, Sex, AgeBin [lo,hi), and a numeric value column. Sex accepts: 0/f/female, 1/m/male. |
art_initiation |
bernoulli(0.9) | Probability a newly diagnosed person initiates ART. Set to 1 to treat all diagnosed. |
pmtct_efficacy |
0.96 | Efficacy of maternal ART in reducing infant susceptibility to HIV, applied to both prenatal (MaternalNet) and postnatal (BreastfeedingNet) transmission. |
smoothness |
0 | Interpolation smoothness for coverage data (0 = linear, higher = smoother S-curves). |
format_priority |
‘n’ | When both n_art and p_art are non-NaN, prefer this format ('n' or 'p'). |
VMMC
Voluntary medical male circumcision. Targets all males by default; reduces susceptibility to HIV acquisition by 60%.
vmmc = sti.VMMC(coverage=0.3)
vmmc = sti.VMMC(coverage={'year': [2010, 2025], 'value': [0, 0.4]})| Parameter | Default | Description |
|---|---|---|
coverage |
None (does nothing without coverage data) | Coverage target (same formats as ART). Required for VMMC to have any effect. |
eff_circ |
0.6 | Efficacy (60% reduction in HIV acquisition risk) |
eligibility |
all males | Optional function to further restrict who is eligible |
PrEP
Pre-exposure prophylaxis. By default targets HIV-negative FSWs (female sex workers) who are not already on PrEP. Use the eligibility parameter to target a different population.
prep = sti.Prep(coverage=[0, 0.5], years=[2020, 2025])| Parameter | Default | Description |
|---|---|---|
coverage |
[0, 0.01, 0.5, 0.8] | Coverage values at each year (linearly interpolated between years) |
years |
[2004, 2005, 2015, 2025] | Calendar years corresponding to coverage values |
eff_prep |
0.8 | Efficacy (80% reduction in HIV acquisition risk) |
eligibility |
HIV-negative FSWs not on PrEP | Optional function to override default targeting |
Combining interventions
Interventions are passed to the sim as a list:
sim = sti.Sim(
diseases='ng',
interventions=[test, treatment],
)For a complete worked example, see the Interventions tutorial.