Skip to content

HMB treatment pool model#44

Open
nnoori-IDM wants to merge 5 commits into
mainfrom
hmb-pool-model-nn
Open

HMB treatment pool model#44
nnoori-IDM wants to merge 5 commits into
mainfrom
hmb-pool-model-nn

Conversation

@nnoori-IDM
Copy link
Copy Markdown
Collaborator

This PR replaces the sequential cascade (NSAID → TXA → Pill → hIUD) with a pool-based treatment assignment model (interventions_pool.py). When a woman seeks care, she now draws from a weighted distribution across all four treatments, with ineligible options (tried-and-failed, fertility intent for Pill/hIUD) zeroed out and weights renormalized per person before drawing. A global prob_offer gate controls the fraction of seekers who receive any treatment, applied once before the treatment draw rather than per-treatment after.

Treatment weights and prob_offer support pre/post switching at the intervention year (2026), so all simulations run identical status quo parameters (10% ever-seek, 70% offered, 50/25/25 NSAID/TXA/Pill, no hIUD) before the switch and diverge to scenario-specific parameters after. The care-seeking architecture retains: Layer 1 (ever-seeker gate), Layer 2 (monthly arrival with condition-specific rates for anemia/pain), and Layer 3 (episode cap at 3 visits with 90% stop seeking/ 10% hysterectomy routing).

run_anemia_risk_sensitivity.py and run_scenarios.py: Include updated sensitivity analysis (3 RR of anemia given HMB levels × 3 hIUD uptake levels), a full 3×3 scenario comparison (3 care-seeking levels × 3 hIUD levels vs counterfactual), and a treatment usage code tracking care visits etc.

The calibrate_p_hmb.py code calibrates HMB prevalence so that with status quo treatment, observed HMB prevalence ≈ 48%.

@nnoori-IDM nnoori-IDM requested a review from robynstuart April 27, 2026 16:09
Copy link
Copy Markdown
Collaborator

@robynstuart robynstuart left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nnoori-IDM this looks great, sorry it took me so long to review! The pooling logic seems very intuitive to me. I have a few comments for your consideration but nothing urgent or blocking.

Comment thread interventions.py
care_seeking_dist = ss.normal(1, 1),

efficacy=0.8,
efficacy=0.88,
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these values are very specific - are they from the literature, or calibrated?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, they come from the literature. Slide 11 of the deck below shows their effectiveness range:
https://bmgf-my.sharepoint.com/:p:/g/personal/navideh_noori_gatesfoundation_org/IQCS3IB_AmgsRrL3Fdo-YMeCATV3aTzFkEhi6c0Nq-p471k?e=fvLcoO

Comment thread interventions_pool.py

Layer 1 — Ever-seeker (one-time draw at HMB onset):
Bernoulli(p_ever_seek). Women who get False NEVER enter the care
system. This controls the "% who will ever seek care" (10–35%).
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so low!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is. This was informed by the PST. Most studies in HIC reported ~60% of HMB women saw a health professional. We assumed the care seeking rate would be lower in Africa.

Comment thread interventions_pool.py
repeat visits, and condition-specific rates for anemia/pain:

First visit:
Base: p_monthly_first (default 1/36 ≈ 3yr lag)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would consider setting this up as a single p_monthly parameter with covariates for pain and anemia. But that's just personal preference, this way works well too.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another thought here which we can check later is that ideally, these things should all be handled by starsim as monthly probabilities meaning that they'll scale appropriately if you change the timestep of the model. Not that you're likely to do that though, I'm sure monthly makes most sense.

Comment thread interventions_pool.py
(Missing a daily pill or removing IUD ends the treatment.)

Adherence values (from Darcy):
NSAID: 80% | TXA: 70% | Pill: 80% | hIUD: 100%
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting that the pill is only 80%. I don't think that this is captured within FPSim but probably should be as it would have implications for its effectiveness as a contraception

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These values are largely assumption-driven, but the pill burden is high for TXA and there can be side effects,
suggesting lower adherence. (e.g., Ling et al. 2019: https://www.sciencedirect.com/science/article/pii/S0006497118637942).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is largely assumption-driven, but the pill burden is high for TXA and there can be side effects,
suggesting lower adherence. (e.g., Ling et al. 2019: https://www.sciencedirect.com/science/article/pii/S0006497118637942).

Comment thread interventions_pool.py
POOL vs CASCADE
=============================================================================

Pool (primary):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This makes much more sense to me as a realistic delivery setup

Comment thread interventions_pool.py
# ── Orchestrator-level states ──
self.define_states(
# Layer 1
ss.BoolState('ever_seeker'),
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I'm kind of leaning towards a design where you have to have an orchestrator class, so this is just set once in a nice clear way and you don't need the standalone fallbacks

Comment thread interventions_pool.py
self._tx_probs_pre = self._normalize_weights(self.pars.tx_weights_pre)
self._tx_probs_post = self._normalize_weights(self.pars.tx_weights_post)

def _normalize_weights(self, weights):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might be good to raise a warning for users if their weights get renomarlized

Comment thread interventions_pool.py
8. Check adherence
9. Continuation checks (use-at-will)
"""
if self.sim.t.now() < self.pars.year:
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment as above

Comment thread interventions_pool.py

# Draw one treatment per person
draws = np.array([
np.random.choice(self._n_tx, p=p) for p in draw_probs
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will break CRN - could talk to claude about replacing it with ss.choice

Comment thread interventions_pool.py
# This is the baseline that intervention scenarios are compared against.
# ============================================================================

class HMBCounterfactual(HMBPool):
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this actually a separate class, or just an instance of the pool with particular parameter values?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the latter.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants