Model integrated postpartum care
Postpartum women in many settings face two concurrent needs: contraception to space or limit births, and HIV prevention during a period of elevated exposure. A single postnatal visit can offer both. This how-to shows how to model a co-delivery intervention – long-acting injectable contraception plus long-acting PrEP – at the same 2-month postpartum contact, using FPsim for the family planning side and STIsim’s HIV module for the PrEP side.
You will need FPsim and STIsim installed.
Sim skeleton
FPmod exposes ti_live_birth on every woman, which is the timestep of her most recent live birth. That is the natural hook for a postpartum intervention: on any given timestep, the eligible cohort is the women whose ti_live_birth was exactly visit_month timesteps ago and who are not currently pregnant.
Wire FPsim, STIsim HIV, a structured sexual network, and a maternal network into one sim, then hand the postpartum cohort to a small custom intervention:
import starsim as ss
import stisim as sti
import fpsim as fp
def make_sim(seed):
hiv = sti.HIV(init_prev=0.05, beta_m2f=0.012)
intvs = [sti.HIVTest(test_prob_data=0.9), sti.ART(coverage=0.9),
PostnatalPackage(offer_fp=True, offer_prep=True)]
return fp.Sim(
location='kenya', n_agents=20_000, start_year=2000, end_year=2035, rand_seed=seed,
diseases=hiv,
networks=[sti.StructuredSexual(), ss.MaternalNet()],
interventions=intvs,
)The intervention
PostnatalPackage is a small custom ss.Intervention that identifies women at visit_month timesteps postpartum, draws attendance, and offers the two services to the same attendees:
class PostnatalPackage(ss.Intervention):
"""Offer long-acting injectable contraception and/or long-acting PrEP at a postnatal visit."""
def __init__(self, pars=None, name='pp_shop', offer_fp=True, offer_prep=True, **kwargs):
super().__init__(name=name)
self.offer_fp = offer_fp
self.offer_prep = offer_prep
self.define_pars(
visit_month=2,
start_year=2027,
p_attend=ss.bernoulli(p=0.5),
p_fp_uptake=ss.bernoulli(p=0.3),
p_prep_uptake=ss.bernoulli(p=0.3),
prep_eff=0.95,
dur_prep=ss.normal(ss.months(6), ss.months(1)),
)
self.update_pars(pars, **kwargs)
def init_pre(self, sim):
super().init_pre(sim)
self.fp_mod = sim.demographics.fp
self.cm = sim.connectors.contraception
self.inj_idx = self.cm.methods['inj'].idx
self.hiv = sim.diseases.get('hiv') if self.offer_prep else None
def step(self):
if self.sim.t.now('year') < self.pars.start_year:
return
fp_mod = self.fp_mod
target_ti = self.sim.ti - self.pars.visit_month
eligible = ((fp_mod.ti_live_birth == target_ti) & ~fp_mod.pregnant).uids
attendees = self.pars.p_attend.filter(eligible)
if not len(attendees):
return
if self.offer_fp:
not_on_method = attendees[~fp_mod.on_contra[attendees]]
starters = self.pars.p_fp_uptake.filter(not_on_method)
fp_mod.on_contra[starters] = True
fp_mod.method[starters] = self.inj_idx
fp_mod.ti_contra[starters] = self.sim.ti + self.cm.set_dur_method(starters)
if self.offer_prep:
hiv_neg = attendees[~self.hiv.infected[attendees] & ~self.hiv.on_prep[attendees]]
starters = self.pars.p_prep_uptake.filter(hiv_neg)
dur = ss.years(self.pars.dur_prep.rvs(starters))
self.hiv.start_prep(starters, eff=self.pars.prep_eff, dur=dur, adh=1.0)Use ss.bernoulli and ss.normal for the draws (not numpy.random) so common-random- number pairing across scenarios stays intact.
What to look at
Run the sim once for each scenario you want to compare (typical grid: baseline, FP-only, PrEP-only, both) with several stochastic seeds, then extract:
sim.results.fp.short_intervals– births at <24 mo interpregnancy interval.sim.results.hiv.new_infections_f– new HIV infections among women.
Both are annualized in the standard way (.annualize()) before you take differences against baseline.
Gotchas
- Visit at month 2, not month 1. FPmod sets
ti_contra = ti + 1at delivery, so the contraception connector re-chooses a method at month 1. If the intervention fires at month 1 the choice is overwritten immediately. Month 2 is safe. - FPsim sexual activity and the STIsim sexual network are independent. A woman can conceive without a current partner in
StructuredSexual, and vice versa. Acceptable for an illustrative composition; call it out if you use the pattern for anything quantitative. - HIV calibration matters.
sti.HIVneeds realisticinit_prevandbeta_m2ffor the setting, or the PrEP arm’s effect will be measured against an implausible baseline.
Full reference project
A calibrated Kenya version of this analysis lives at hiv_fp_kenya – the intervention above is demo.py:PostnatalPackage, and the full 4-scenario grid, calibrated HIV parameters, Kenya scale factor, and two-panel figure are all in the repo.