Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
67a2aed
updating parameters
mmsuarezcosta Jul 30, 2025
c2ea5f4
ensure parameters set before calling them, remove hardcoding of reduc…
mmsuarezcosta Jul 30, 2025
a297a2f
add design feature
mmsuarezcosta Aug 1, 2025
ede9125
put back hardcoding of DALYs
mmsuarezcosta Aug 1, 2025
fea435b
add variables in param file
mmsuarezcosta Aug 1, 2025
9dfcc68
not used
mmsuarezcosta Aug 1, 2025
b075e93
update csv files
mmsuarezcosta Aug 1, 2025
987612c
update files based on latest definitions
mmsuarezcosta Aug 3, 2025
0c9c9fe
revert to original
mmsuarezcosta Aug 3, 2025
04329ab
remove design feature option
mmsuarezcosta Aug 4, 2025
3c623ad
improve documentation
mmsuarezcosta Aug 4, 2025
36ad934
remove unused parameters variable
mmsuarezcosta Aug 4, 2025
6024290
linting fixes
mmsuarezcosta Aug 4, 2025
0c934d8
linting fixes
mmsuarezcosta Aug 4, 2025
165b11c
tox test
mmsuarezcosta Aug 4, 2025
d6dbd5a
tox edits (revert imports to original file formatting)
mmsuarezcosta Aug 4, 2025
72c2169
tox fix
mmsuarezcosta Aug 5, 2025
2cfea7c
tox fix
mmsuarezcosta Aug 5, 2025
6403e12
tox fix
mmsuarezcosta Aug 5, 2025
a9e7e7d
ruff
mmsuarezcosta Aug 6, 2025
f86d8a5
fix reading parameters
mmsuarezcosta Aug 6, 2025
d7d8994
tox fix
mmsuarezcosta Aug 7, 2025
64dc145
rename column
mmsuarezcosta Aug 13, 2025
c034343
change col name to 'reference'
mmsuarezcosta Aug 13, 2025
a66561c
update param documentation
mmsuarezcosta Aug 15, 2025
81203ed
add main polling event frequency
mmsuarezcosta Aug 15, 2025
ac39605
update from Marg. latest comments
mmsuarezcosta Aug 21, 2025
4ca6a32
update param labels and fix sympt_resolution_after_treatment
mmsuarezcosta Aug 21, 2025
964ba88
update phase shift and priod labeling
mmsuarezcosta Aug 25, 2025
c80df7a
Merge branch 'master' into 1649-measles
tbhallett Sep 22, 2025
9e0ee0d
allow manual labelling of parameters loaded from seperate sheets
tbhallett Sep 22, 2025
5962367
add error handling
tbhallett Sep 22, 2025
cb54fe5
declare case_fatality_rate
tbhallett Sep 22, 2025
e648713
handle warnings
tbhallett Sep 22, 2025
eee3b19
Merge remote-tracking branch 'origin/1649-measles' into 1649-measles
tbhallett Sep 22, 2025
96bf81a
fix linting errors, change type to REAL for whole numbers
mmsuarezcosta Oct 23, 2025
74ce660
add param - latest date measles onset can be in random date generator…
mmsuarezcosta Nov 9, 2025
017278f
update comment for clarity
mmsuarezcosta Nov 9, 2025
dabee71
Merge branch 'master' into 1649-measles
mnjowe Dec 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions resources/ResourceFile_Measles/beta.csv

This file was deleted.

4 changes: 2 additions & 2 deletions resources/ResourceFile_Measles/cfr.csv
Git LFS file not shown
4 changes: 2 additions & 2 deletions resources/ResourceFile_Measles/parameters.csv
Git LFS file not shown
4 changes: 2 additions & 2 deletions resources/ResourceFile_Measles/symptoms.csv
Git LFS file not shown
93 changes: 70 additions & 23 deletions src/tlo/methods/measles.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,49 @@ class Measles(Module, GenericFirstAppointmentsMixin):
Types.REAL, "Efficacy of first measles vaccine dose against measles infection"),
"vaccine_efficacy_2": Parameter(
Types.REAL, "Efficacy of second measles vaccine dose against measles infection"),
"prob_severe": Parameter(
Types.REAL, "Probability of severe measles infection, requiring hospitalisation"),
"risk_death_on_treatment": Parameter(
Types.REAL, "Risk of scheduled death occurring if on treatment for measles complications"),
"symptom_prob": Parameter(
Types.DATA_FRAME, "Probability of each symptom with measles infection"),
"case_fatality_rate": Parameter(
Types.DICT, "Probability that case of measles will result in death if not treated")
"odds_ratio_health_seeking_in_children_rash": Parameter(
Types.REAL, "Odds ratio seeking care in children rash"),
"odds_ratio_health_seeking_in_adults_rash": Parameter(
Types.REAL, "Odds ratio seeking care in adults rash"),
"odds_ratio_health_seeking_in_children_otitis_media": Parameter(
Types.REAL, "Odds ratio seeking care in children otitis media"),
"odds_ratio_health_seeking_in_adults_otitis_media": Parameter(
Types.REAL, "Odds ratio seeking care in adults otitis media"),
"reduction_in_death_risk_measle_treated": Parameter(
Types.REAL, "Reduction in death risk when measles are treated"
),
"maternal_immunity_age_threshold": Parameter(
Types.REAL, "Age threshold below which children are protected by maternal immunity"
),
"age_min_symptoms_constant_rate": Parameter(
Types.REAL, "Maximum age for measles symptom probability lookup"
),
"death_timing_min_days": Parameter(
Types.REAL, "Minimum days from symptom onset to death"
),
"death_timing_max_days": Parameter(
Types.REAL, "Maximum days from symptom onset to death"
),
"natural_resolution_min_days": Parameter(
Types.REAL, "Minimum days from symptom onset to natural resolution"
),
"natural_resolution_max_days": Parameter(
Types.REAL, "Maximum days from symptom onset to natural resolution"
),
"symptom_onset_min_days": Parameter(
Types.REAL, "Minimum days from infection to symptom onset (incubation period)"
),
"symptom_onset_max_days": Parameter(
Types.REAL, "Maximum days from infection to symptom onset (incubation period)"
),
"sympt_resolution_after_treatment": Parameter(
Types.REAL, "Days until symptom resolution after treatment"
),
"main_polling_event_frequency_months": Parameter(
Types.REAL, "Measles main polling event frequency months"
Comment thread
mmsuarezcosta marked this conversation as resolved.
Outdated
)
}

PROPERTIES = {
Expand Down Expand Up @@ -108,6 +143,8 @@ def read_parameters(self, resourcefilepath: Optional[Path] = None):
self.parameters["case_fatality_rate"] = workbook["cfr"].set_index('age')["probability"].to_dict()

# moderate symptoms all mapped to moderate_measles, pneumonia/encephalitis mapped to severe_measles

p = self.parameters
if "HealthBurden" in self.sim.modules.keys():
self.parameters["daly_wts"] = {
"rash": self.sim.modules["HealthBurden"].get_daly_weight(sequlae_code=205),
Expand All @@ -122,14 +159,14 @@ def read_parameters(self, resourcefilepath: Optional[Path] = None):
# Declare symptoms that this module will cause and which are not included in the generic symptoms:
self.sim.modules['SymptomManager'].register_symptom(
Symptom(name='rash',
odds_ratio_health_seeking_in_children=2.5,
odds_ratio_health_seeking_in_adults=2.5) # non-emergencies
odds_ratio_health_seeking_in_children=p['odds_ratio_health_seeking_in_children_rash'],
odds_ratio_health_seeking_in_adults=p['odds_ratio_health_seeking_in_adults_rash'])
)

self.sim.modules['SymptomManager'].register_symptom(
Symptom(name='otitis_media',
odds_ratio_health_seeking_in_children=2.5,
odds_ratio_health_seeking_in_adults=2.5) # non-emergencies
odds_ratio_health_seeking_in_children=p['odds_ratio_health_seeking_in_children_otitis_media'],
odds_ratio_health_seeking_in_adults=p['odds_ratio_health_seeking_in_adults_otitis_media'])
)

self.sim.modules['SymptomManager'].register_symptom(Symptom.emergency('encephalitis'))
Expand All @@ -149,6 +186,7 @@ def initialise_population(self, population):

def initialise_simulation(self, sim):
"""Schedule measles event to start straight away. Each month it will assign new infections"""

sim.schedule_event(MeaslesEvent(self), sim.date)
sim.schedule_event(MeaslesLoggingEvent(self), sim.date)
sim.schedule_event(MeaslesLoggingFortnightEvent(self), sim.date)
Expand All @@ -165,6 +203,7 @@ def initialise_simulation(self, sim):
"oxygen cylinders")
}


def on_birth(self, mother_id, child_id):
"""Initialise our properties for a newborn individual
assume all newborns are uninfected
Expand Down Expand Up @@ -196,12 +235,13 @@ def process_parameters(self):
"""Process the parameters (following being read-in) prior to the simulation starting.
Make `self.symptom_probs` to be a dictionary keyed by age, with values of dictionaries keyed by symptoms and
the probability of symptom onset."""
p = self.parameters
probs = self.parameters["symptom_prob"].set_index(["age", "symptom"])["probability"]
self.symptom_probs = {level: probs.loc[(level, slice(None))].to_dict() for level in probs.index.levels[0]}

# Check that a sensible value for a probability of symptom onset is declared for each symptom and for each age
# up to and including age 30
for _age in range(30 + 1):
# up to and including age for which symptom probabilities stabilizes
Comment thread
mnjowe marked this conversation as resolved.
Outdated
for _age in range(int(p["age_min_symptoms_constant_rate"]) + 1):
Comment thread
mnjowe marked this conversation as resolved.
Outdated
assert set(self.symptoms) == set(self.symptom_probs.get(_age).keys())
assert all([0.0 <= x <= 1.0 for x in self.symptom_probs.get(_age).values()])

Expand All @@ -225,7 +265,8 @@ class MeaslesEvent(RegularEvent, PopulationScopeEventMixin):
"""

def __init__(self, module):
super().__init__(module, frequency=DateOffset(months=1))
p = module.parameters
super().__init__(module, frequency=DateOffset(months=p['main_polling_event_frequency_months']))
Comment thread
mnjowe marked this conversation as resolved.
assert isinstance(module, Measles)

def apply(self, population):
Expand All @@ -248,8 +289,8 @@ def apply(self, population):
protected_by_vaccine.loc[(df.va_measles == 1)] *= (1 - p["vaccine_efficacy_1"]) # partially susceptible
protected_by_vaccine.loc[(df.va_measles > 1)] *= (1 - p["vaccine_efficacy_2"]) # partially susceptible

# Find persons to be newly infected (no risk to children under 6 months as protected by maternal immunity)
new_inf = df.index[~df.me_has_measles & (df.age_exact_years >= 0.5) &
# Find persons to be newly infected (no risk to children under maternal immunity threshold )
new_inf = df.index[~df.me_has_measles & (df.age_exact_years >= p["maternal_immunity_age_threshold"]) &
(rng.random_sample(size=len(df)) < (trans_prob * protected_by_vaccine))]

logger.debug(key="MeaslesEvent",
Expand All @@ -274,11 +315,12 @@ def apply(self, person_id):

df = self.sim.population.props # shortcut to the dataframe
rng = self.module.rng
p = self.module.parameters

if not df.at[person_id, "is_alive"]:
return

ref_age = df.at[person_id, "age_years"].clip(max=30) # (For purpose of look-up age limit is 30 years)
ref_age = df.at[person_id, "age_years"].clip(max=p["age_min_symptoms_constant_rate"])

# Determine if the person has "untreated HIV", which is defined as a person in any stage of HIV but not on
# successful treatment currently.
Expand All @@ -303,21 +345,26 @@ def apply(self, person_id):

# schedule the death
self.sim.schedule_event(
death_event, symp_onset + DateOffset(days=rng.randint(3, 7)))
death_event, symp_onset +
DateOffset(days=rng.randint(p["death_timing_min_days"], p["death_timing_max_days"])))

else:
# schedule symptom resolution without treatment - this only occurs if death doesn't happen first
symp_resolve = symp_onset + DateOffset(days=rng.randint(7, 14))
symp_resolve = (symp_onset +
DateOffset(days=rng.randint(p["natural_resolution_min_days"],
p["natural_resolution_max_days"])))
self.sim.schedule_event(MeaslesSymptomResolveEvent(self.module, person_id), symp_resolve)

def assign_symptoms(self, _age):
"""Assign symptoms for this case and returns the date on which symptom onset.
(Parameter values specify that everybody gets rash, fever and eye complain.)"""

rng = self.module.rng
p = self.module.parameters
person_id = self.target
symptom_probs_for_this_person = self.module.symptom_probs.get(_age)
date_of_symp_onset = self.sim.date + DateOffset(days=rng.randint(7, 21))
date_of_symp_onset = self.sim.date + DateOffset(days=rng.randint(p["symptom_onset_min_days"],
p["symptom_onset_max_days"]))

symptoms_to_onset = [
_symp for (_symp, _prob), _rand in zip(
Expand Down Expand Up @@ -379,6 +426,7 @@ def __init__(self, module, person_id):

def apply(self, person_id):
df = self.sim.population.props
p = self.module.parameters

if not df.at[person_id, "is_alive"]:
return
Expand All @@ -389,10 +437,8 @@ def apply(self, person_id):

if df.at[person_id, "me_on_treatment"]:

reduction_in_death_risk = 0.6

# Certain death (100%) is reduced by specified amount
p_death_with_treatment = 1. - reduction_in_death_risk
p_death_with_treatment = 1. - p['reduction_in_death_risk_measle_treated']

# If below that probability, death goes ahead
if self.module.rng.random_sample() < p_death_with_treatment:
Expand Down Expand Up @@ -438,6 +484,7 @@ def apply(self, person_id, squeeze_factor):

df = self.sim.population.props
symptoms = self.sim.modules["SymptomManager"].has_what(person_id=person_id)
p = self.module.parameters

# for non-complicated measles
item_codes = [self.module.consumables['vit_A']]
Expand All @@ -463,7 +510,7 @@ def apply(self, person_id, squeeze_factor):

# schedule symptom resolution following treatment
self.sim.schedule_event(MeaslesSymptomResolveEvent(self.module, person_id),
self.sim.date + DateOffset(days=7))
self.sim.date + DateOffset(days=p["sympt_resolution_after_treatment"]))

def did_not_run(self):
logger.debug(key="HSI_Measles_Treatment",
Expand Down
Loading