Skip to content

Commit

Permalink
Albrja/mic 5805/cpapa implementation (#36)
Browse files Browse the repository at this point in the history
Albrja/mic 5805/cpapa implementation

No CPAP risk factor implementation
- *Category*: Implemenation
- *JIRA issue*: https://jira.ihme.washington.edu/browse/MIC-5805
- *Research reference*: https://vivarium-research.readthedocs.io/en/latest/models/intervention_models/neonatal/cpap_intervention.html#calibration-strategy

Changes and notes
-adds Intrapartum component to determine delivery facility type and CPAP availability for simulants
-adds No CPAP risk effect component to modify preterm with rds CSMR pipeline

### Verification and Testing
<!--
Details on how code was verified. Consider: plots, images, (small) csv files.
-->
  • Loading branch information
albrja authored Mar 5, 2025
1 parent 5154244 commit 23b72a4
Show file tree
Hide file tree
Showing 9 changed files with 204 additions and 45 deletions.
2 changes: 2 additions & 0 deletions src/vivarium_gates_mncnh/components/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from vivarium_gates_mncnh.components.antenatal_care import AntenatalCare
from vivarium_gates_mncnh.components.intervention import NoCPAPRisk
from vivarium_gates_mncnh.components.intrapartum import Intrapartum
from vivarium_gates_mncnh.components.lbwsg import (
LBWSGPAFCalculationExposure,
LBWSGPAFCalculationRiskEffect,
Expand Down
76 changes: 76 additions & 0 deletions src/vivarium_gates_mncnh/components/intervention.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from __future__ import annotations

from functools import partial

import pandas as pd
from vivarium import Component
from vivarium.framework.engine import Builder
from vivarium.framework.event import Event

from vivarium_gates_mncnh.constants import data_values
from vivarium_gates_mncnh.constants.data_keys import NO_CPAP_RISK
from vivarium_gates_mncnh.constants.data_values import COLUMNS, PIPELINES
from vivarium_gates_mncnh.utilities import get_location


class NoCPAPRisk(Component):
"""Component for CPAP intervention. This is essentially a risk effect."""

@property
def configuration_defaults(self) -> dict:
return {
self.name: {
"data_sources": {
"relative_risk": NO_CPAP_RISK.RELATIVE_RISK,
"paf": self.load_paf_data,
}
}
}

@property
def columns_required(self) -> list[str]:
return [COLUMNS.CPAP_AVAILABLE]

def __init__(self) -> None:
super().__init__()
self.preterm_csmr_target = PIPELINES.NEONATAL_PRETERM_BIRTH_WITH_RDS

def setup(self, builder: Builder) -> None:
self.randomness = builder.randomness.get_stream(self.name)
self.location = get_location(builder)
self.preterm_with_rds_csmr = builder.value.get_value(self.preterm_csmr_target)
builder.value.register_value_modifier(
self.preterm_with_rds_csmr.name,
self.modify_preterm_with_rds_csmr,
required_resources=[
self.preterm_with_rds_csmr.name,
COLUMNS.DELIVERY_FACILITY_TYPE,
COLUMNS.CPAP_AVAILABLE,
],
)

##################
# Helper nethods #
##################

def load_paf_data(self, builder: Builder) -> pd.Series:
data = builder.data.load(NO_CPAP_RISK.PAF)
data = data.rename(columns=data_values.CHILD_LOOKUP_COLUMN_MAPPER)
return data

def modify_preterm_with_rds_csmr(
self, index: pd.Index, preterm_with_rds_csmr: pd.Series[float]
) -> pd.Series[float]:
# No CPAP access is like a dichotomous risk factor, meaning those that have access to CPAP will
# not have their CSMR modify by no CPAP RR
pop = self.population_view.get(index)
no_cpap_idx = pop.index[pop[COLUMNS.CPAP_AVAILABLE] == False]
# NOTE: RR is relative risk for no CPAP
no_cpap_rr = self.lookup_tables["relative_risk"](no_cpap_idx)
# NOTE: PAF is for no CPAP
paf = self.lookup_tables["paf"](index)

# Modify the CSMR pipeline
modified_csmr = preterm_with_rds_csmr * (1 - paf)
modified_csmr.loc[no_cpap_idx] = modified_csmr * no_cpap_rr
return modified_csmr
80 changes: 80 additions & 0 deletions src/vivarium_gates_mncnh/components/intrapartum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import pandas as pd
from vivarium import Component
from vivarium.framework.engine import Builder
from vivarium.framework.event import Event
from vivarium.framework.population import SimulantData

from vivarium_gates_mncnh.constants.data_keys import NO_CPAP_RISK
from vivarium_gates_mncnh.constants.data_values import (
COLUMNS,
CPAP_ACCESS_PROBABILITIES,
DELIVERY_FACILITY_TYPE_PROBABILITIES,
DELIVERY_FACILITY_TYPES,
SIMULATION_EVENT_NAMES,
)
from vivarium_gates_mncnh.utilities import get_location


class Intrapartum(Component):
"""This component functions to make key decisions about events in the intrapartum step of the model.
Specifically, it determines the delivery facility type and whether or not CPAP is available to the simulant."""

@property
def columns_created(self) -> list[str]:
return [
COLUMNS.DELIVERY_FACILITY_TYPE,
COLUMNS.CPAP_AVAILABLE,
]

def setup(self, builder: Builder) -> None:
self._sim_step_name = builder.time.simulation_event_name()
self.randomness = builder.randomness.get_stream(self.name)
self.location = get_location(builder)

def on_initialize_simulants(self, pop_data: SimulantData) -> None:
anc_data = pd.DataFrame(
{
COLUMNS.DELIVERY_FACILITY_TYPE: DELIVERY_FACILITY_TYPES.NONE,
COLUMNS.CPAP_AVAILABLE: False,
},
index=pop_data.index,
)
self.population_view.update(anc_data)

def on_time_step(self, event: Event) -> None:
if self._sim_step_name() != SIMULATION_EVENT_NAMES.INTRAPARTUM:
return

pop = self.population_view.get(event.index)
# Choose delivery facility type
delivery_facility_type = self.randomness.choice(
pop.index,
[
DELIVERY_FACILITY_TYPES.HOME,
DELIVERY_FACILITY_TYPES.CEmONC,
DELIVERY_FACILITY_TYPES.BEmONC,
],
p=list(DELIVERY_FACILITY_TYPE_PROBABILITIES[self.location].values()),
additional_key="delivery_facility_type",
)
pop[COLUMNS.DELIVERY_FACILITY_TYPE] = delivery_facility_type

# Determine if simulant had access to CPAP
facility_type_mapper = {
DELIVERY_FACILITY_TYPES.BEmONC: NO_CPAP_RISK.P_CPAP_BEmONC,
DELIVERY_FACILITY_TYPES.CEmONC: NO_CPAP_RISK.P_CPAP_CEmONC,
}
for facility_type in [
DELIVERY_FACILITY_TYPES.BEmONC,
DELIVERY_FACILITY_TYPES.CEmONC,
]:
facility_idx = pop.index[pop[COLUMNS.DELIVERY_FACILITY_TYPE] == facility_type]
cpap_access_probability = CPAP_ACCESS_PROBABILITIES[self.location][
facility_type_mapper[facility_type]
]
cpap_access_idx = self.randomness.filter_for_probability(
facility_idx, cpap_access_probability, f"cpap_access_{facility_type}"
)
pop.loc[cpap_access_idx, COLUMNS.CPAP_AVAILABLE] = True

self.population_view.update(pop)
4 changes: 1 addition & 3 deletions src/vivarium_gates_mncnh/components/neonatal_causes.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import pandas as pd
from vivarium import Component
from vivarium.framework.engine import Builder
from vivarium.framework.event import Event
from vivarium.framework.resource import Resource

from vivarium_gates_mncnh.constants import data_keys
from vivarium_gates_mncnh.constants.data_values import (
Expand Down Expand Up @@ -37,7 +35,7 @@ def __init__(self, neonatal_cause: str) -> None:
# Lifecycle methods #
#####################

def setup(self, builder):
def setup(self, builder: Builder) -> None:
self.acmr_paf = builder.value.get_value(PIPELINES.ACMR_PAF)
# Register csmr pipeline
self.csmr = builder.value.register_value_producer(
Expand Down
1 change: 0 additions & 1 deletion src/vivarium_gates_mncnh/components/observers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
CHILD_INITIALIZATION_AGE,
COLUMNS,
MATERNAL_DISORDERS,
NEONATAL_CAUSES,
PREGNANCY_OUTCOMES,
SIMULATION_EVENT_NAMES,
)
Expand Down
6 changes: 3 additions & 3 deletions src/vivarium_gates_mncnh/constants/data_keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def log_name(self):
NEONATAL_ENCEPHALOPATHY = __NeonatalEncephalopath()


class __NoCPAPIntervention(NamedTuple):
class __NoCPAPRisk(NamedTuple):
# Keys that will be loaded into the artifact. must have a colon type declaration
P_RDS: str = "intervention.no_cpap_intervention.p_rds"
P_HOME: str = "intervention.no_cpap_intervention.probability_home_birth"
Expand All @@ -224,7 +224,7 @@ def log_name(self):
return "no CPAP intervention"


NO_CPAP_INTERVENTION = __NoCPAPIntervention()
NO_CPAP_RISK = __NoCPAPRisk()


MAKE_ARTIFACT_KEY_GROUPS = [
Expand All @@ -239,5 +239,5 @@ def log_name(self):
PRETERM_BIRTH,
NEONATAL_SEPSIS,
NEONATAL_ENCEPHALOPATHY,
NO_CPAP_INTERVENTION,
NO_CPAP_RISK,
]
42 changes: 22 additions & 20 deletions src/vivarium_gates_mncnh/constants/data_values.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import datetime
from typing import NamedTuple

from vivarium_gates_mncnh.constants.data_keys import NO_CPAP_INTERVENTION
from vivarium_gates_mncnh.constants.data_keys import NO_CPAP_RISK

############################
# Disease Model Parameters #
Expand Down Expand Up @@ -80,7 +80,7 @@ class _Durations(NamedTuple):
class _SimulationEventNames(NamedTuple):
# Constants for the simulation events. Used for string comparison in components.
PREGNANCY = "pregnancy"
INTRAPARTRUM = "intrapartum"
INTRAPARTUM = "intrapartum"
MATERNAL_SEPSIS = "maternal_sepsis_and_other_maternal_infections"
MATERNAL_HEMORRHAGE = "maternal_hemorrhage"
OBSTRUCTED_LABOR = "maternal_obstructed_labor_and_uterine_rupture"
Expand Down Expand Up @@ -161,6 +161,7 @@ class __Columns(NamedTuple):
BIRTH_WEIGHT = "birth_weight"
GESTATIONAL_AGE = "gestational_age"
ATTENDED_CARE_FACILITY = "attended_care_facility"
DELIVERY_FACILITY_TYPE = "delivery_facility_type"
RECEIVED_ULTRASOUND = "received_ultrasound"
ULTRASOUND_TYPE = "ultrasound_type"
STATED_GESTATIONAL_AGE = "stated_gestational_age"
Expand All @@ -169,6 +170,7 @@ class __Columns(NamedTuple):
MATERNAL_SEPSIS = "maternal_sepsis_and_other_maternal_infections"
MATERNAL_HEMORRHAGE = "maternal_hemorrhage"
OBSTRUCTED_LABOR = "maternal_obstructed_labor_and_uterine_rupture"
CPAP_AVAILABLE = "cpap_available"


COLUMNS = __Columns()
Expand Down Expand Up @@ -255,36 +257,36 @@ class __DeliveryFacilityTypes(NamedTuple):

DELIVERY_FACILITY_TYPE_PROBABILITIES = {
"Ethiopia": {
NO_CPAP_INTERVENTION.P_HOME: 0.683,
NO_CPAP_INTERVENTION.P_CEmONC: 0.266,
NO_CPAP_INTERVENTION.P_BEmONC: 0.051,
NO_CPAP_RISK.P_HOME: 0.683,
NO_CPAP_RISK.P_CEmONC: 0.266,
NO_CPAP_RISK.P_BEmONC: 0.051,
},
"Nigeria": {
NO_CPAP_INTERVENTION.P_HOME: 0.683,
NO_CPAP_INTERVENTION.P_CEmONC: 0.266,
NO_CPAP_INTERVENTION.P_BEmONC: 0.051,
NO_CPAP_RISK.P_HOME: 0.683,
NO_CPAP_RISK.P_CEmONC: 0.266,
NO_CPAP_RISK.P_BEmONC: 0.051,
},
"Pakistan": {
NO_CPAP_INTERVENTION.P_HOME: 0.683,
NO_CPAP_INTERVENTION.P_CEmONC: 0.266,
NO_CPAP_INTERVENTION.P_BEmONC: 0.051,
NO_CPAP_RISK.P_HOME: 0.683,
NO_CPAP_RISK.P_CEmONC: 0.266,
NO_CPAP_RISK.P_BEmONC: 0.051,
},
}
# Probability each of these facility types has access to CPAP
CPAP_ACCESS_PROBABILITIES = {
"Ethiopia": {
NO_CPAP_INTERVENTION.P_CPAP_BEmONC: 0.075,
NO_CPAP_INTERVENTION.P_CPAP_CEmONC: 0.393,
NO_CPAP_INTERVENTION.P_CPAP_HOME: 0.0,
NO_CPAP_RISK.P_CPAP_BEmONC: 0.075,
NO_CPAP_RISK.P_CPAP_CEmONC: 0.393,
NO_CPAP_RISK.P_CPAP_HOME: 0.0,
},
"Nigeria": {
NO_CPAP_INTERVENTION.P_CPAP_BEmONC: 0.075,
NO_CPAP_INTERVENTION.P_CPAP_CEmONC: 0.393,
NO_CPAP_INTERVENTION.P_CPAP_HOME: 0.0,
NO_CPAP_RISK.P_CPAP_BEmONC: 0.075,
NO_CPAP_RISK.P_CPAP_CEmONC: 0.393,
NO_CPAP_RISK.P_CPAP_HOME: 0.0,
},
"Pakistan": {
NO_CPAP_INTERVENTION.P_CPAP_BEmONC: 0.075,
NO_CPAP_INTERVENTION.P_CPAP_CEmONC: 0.393,
NO_CPAP_INTERVENTION.P_CPAP_HOME: 0.0,
NO_CPAP_RISK.P_CPAP_BEmONC: 0.075,
NO_CPAP_RISK.P_CPAP_CEmONC: 0.393,
NO_CPAP_RISK.P_CPAP_HOME: 0.0,
},
}
34 changes: 17 additions & 17 deletions src/vivarium_gates_mncnh/data/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,15 @@ def get_data(
data_keys.PRETERM_BIRTH.CSMR: load_standard_data,
data_keys.NEONATAL_SEPSIS.CSMR: load_standard_data,
data_keys.NEONATAL_ENCEPHALOPATHY.CSMR: load_standard_data,
data_keys.NO_CPAP_INTERVENTION.P_RDS: load_p_rds,
data_keys.NO_CPAP_INTERVENTION.P_HOME: load_probability_birth_facility_type,
data_keys.NO_CPAP_INTERVENTION.P_BEmONC: load_probability_birth_facility_type,
data_keys.NO_CPAP_INTERVENTION.P_CEmONC: load_probability_birth_facility_type,
data_keys.NO_CPAP_INTERVENTION.P_CPAP_HOME: load_cpap_facility_access_probability,
data_keys.NO_CPAP_INTERVENTION.P_CPAP_BEmONC: load_cpap_facility_access_probability,
data_keys.NO_CPAP_INTERVENTION.P_CPAP_CEmONC: load_cpap_facility_access_probability,
data_keys.NO_CPAP_INTERVENTION.RELATIVE_RISK: load_no_cpap_relative_risk,
data_keys.NO_CPAP_INTERVENTION.PAF: load_no_cpap_paf,
data_keys.NO_CPAP_RISK.P_RDS: load_p_rds,
data_keys.NO_CPAP_RISK.P_HOME: load_probability_birth_facility_type,
data_keys.NO_CPAP_RISK.P_BEmONC: load_probability_birth_facility_type,
data_keys.NO_CPAP_RISK.P_CEmONC: load_probability_birth_facility_type,
data_keys.NO_CPAP_RISK.P_CPAP_HOME: load_cpap_facility_access_probability,
data_keys.NO_CPAP_RISK.P_CPAP_BEmONC: load_cpap_facility_access_probability,
data_keys.NO_CPAP_RISK.P_CPAP_CEmONC: load_cpap_facility_access_probability,
data_keys.NO_CPAP_RISK.RELATIVE_RISK: load_no_cpap_relative_risk,
data_keys.NO_CPAP_RISK.PAF: load_no_cpap_paf,
}
return mapping[lookup_key](lookup_key, location, years)

Expand Down Expand Up @@ -453,14 +453,14 @@ def load_no_cpap_paf(
) -> float:

# Get all no_cpap data for calculations
p_rds = get_data(data_keys.NO_CPAP_INTERVENTION.P_RDS, location, years)
p_home = get_data(data_keys.NO_CPAP_INTERVENTION.P_HOME, location, years)
p_BEmONC = get_data(data_keys.NO_CPAP_INTERVENTION.P_BEmONC, location, years)
p_CEmONC = get_data(data_keys.NO_CPAP_INTERVENTION.P_CEmONC, location, years)
p_CPAP_home = get_data(data_keys.NO_CPAP_INTERVENTION.P_CPAP_HOME, location, years)
p_CPAP_BEmONC = get_data(data_keys.NO_CPAP_INTERVENTION.P_CPAP_BEmONC, location, years)
p_CPAP_CEmONC = get_data(data_keys.NO_CPAP_INTERVENTION.P_CPAP_CEmONC, location, years)
relative_risk = get_data(data_keys.NO_CPAP_INTERVENTION.RELATIVE_RISK, location, years)
p_rds = get_data(data_keys.NO_CPAP_RISK.P_RDS, location, years)
p_home = get_data(data_keys.NO_CPAP_RISK.P_HOME, location, years)
p_BEmONC = get_data(data_keys.NO_CPAP_RISK.P_BEmONC, location, years)
p_CEmONC = get_data(data_keys.NO_CPAP_RISK.P_CEmONC, location, years)
p_CPAP_home = get_data(data_keys.NO_CPAP_RISK.P_CPAP_HOME, location, years)
p_CPAP_BEmONC = get_data(data_keys.NO_CPAP_RISK.P_CPAP_BEmONC, location, years)
p_CPAP_CEmONC = get_data(data_keys.NO_CPAP_RISK.P_CPAP_CEmONC, location, years)
relative_risk = get_data(data_keys.NO_CPAP_RISK.RELATIVE_RISK, location, years)
# rr_cpap = 1 / relative_risk)
# p_rds_cpap = (1 / relative_risk) * p_rds_no_cpap
# p_rds_no_cpap = p_rds_cpap * relative_risk
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ components:
components:
- AgelessPopulation("population.scaling_factor")
- Pregnancy()
- Intrapartum()
- ResultsStratifier()
- BirthObserver()
- AntenatalCare()
Expand All @@ -30,6 +31,7 @@ components:
- NeonatalCause('neonatal_sepsis_and_other_neonatal_infections')
- NeonatalCause('neonatal_encephalopathy_due_to_birth_asphyxia_and_trauma')
- NeonatalMortality()
- NoCPAPRisk()
# Add model observers below here
- ANCObserver()
- MaternalDisordersBurdenObserver()
Expand All @@ -39,7 +41,7 @@ components:
configuration:
input_data:
input_draw_number: 0
artifact_path: "/mnt/team/simulation_science/pub/models/vivarium_gates_mncnh/artifacts/lbwsg/ethiopia.hdf"
artifact_path: "/mnt/team/simulation_science/pub/models/vivarium_gates_mncnh/artifacts/cpap/ethiopia.hdf"
interpolation:
order: 0
extrapolate: True
Expand Down

0 comments on commit 23b72a4

Please sign in to comment.