HMB treatment pool model#44
Conversation
robynstuart
left a comment
There was a problem hiding this comment.
@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.
| care_seeking_dist = ss.normal(1, 1), | ||
|
|
||
| efficacy=0.8, | ||
| efficacy=0.88, |
There was a problem hiding this comment.
these values are very specific - are they from the literature, or calibrated?
There was a problem hiding this comment.
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
|
|
||
| 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%). |
There was a problem hiding this comment.
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.
| repeat visits, and condition-specific rates for anemia/pain: | ||
|
|
||
| First visit: | ||
| Base: p_monthly_first (default 1/36 ≈ 3yr lag) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| (Missing a daily pill or removing IUD ends the treatment.) | ||
|
|
||
| Adherence values (from Darcy): | ||
| NSAID: 80% | TXA: 70% | Pill: 80% | hIUD: 100% |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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).
| POOL vs CASCADE | ||
| ============================================================================= | ||
|
|
||
| Pool (primary): |
There was a problem hiding this comment.
This makes much more sense to me as a realistic delivery setup
| # ── Orchestrator-level states ── | ||
| self.define_states( | ||
| # Layer 1 | ||
| ss.BoolState('ever_seeker'), |
There was a problem hiding this comment.
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
| 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): |
There was a problem hiding this comment.
might be good to raise a warning for users if their weights get renomarlized
| 8. Check adherence | ||
| 9. Continuation checks (use-at-will) | ||
| """ | ||
| if self.sim.t.now() < self.pars.year: |
|
|
||
| # Draw one treatment per person | ||
| draws = np.array([ | ||
| np.random.choice(self._n_tx, p=p) for p in draw_probs |
There was a problem hiding this comment.
this will break CRN - could talk to claude about replacing it with ss.choice
| # This is the baseline that intervention scenarios are compared against. | ||
| # ============================================================================ | ||
|
|
||
| class HMBCounterfactual(HMBPool): |
There was a problem hiding this comment.
is this actually a separate class, or just an instance of the pool with particular parameter values?
There was a problem hiding this comment.
It's the latter.
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%.