Skip to content

Commit 4e540d6

Browse files
committed
HPV Transmission Model Version 1.0
1 parent f465f10 commit 4e540d6

2 files changed

Lines changed: 182 additions & 13 deletions

File tree

src/tlo/methods/hpv.py

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ class HPV(Module, GenericFirstAppointmentsMixin):
6464

6565
HPV_GROUPS = ['hr1', 'hr2', 'hr3']
6666
AGE_BINS = [15, 20, 25, 35, 45, 55, 200]
67-
AGE_LABELS = ['15_19', '20_24', '25_34','35_44', '45_54', '55plus']
67+
AGE_LABELS = ['15_19', '20_24', '25_34', '35_44', '45_54', '55plus']
6868

6969
PARAMETERS = {
7070
"init_prev_hpv_hr1": Parameter(
@@ -147,7 +147,7 @@ class HPV(Module, GenericFirstAppointmentsMixin):
147147
"Rate ratio for HPV clearance among PLWH on ART but not virally suppressed",
148148
),
149149

150-
## As MC suggested, remove the immunity part
150+
# As MC suggested, remove the immunity part
151151
# "rr_immunity_hr1": Parameter(
152152
# Types.REAL,
153153
# "Relative risk for reinfection with hr1 if previously infected",
@@ -180,11 +180,11 @@ class HPV(Module, GenericFirstAppointmentsMixin):
180180
'hp_date_first_infected': Property(
181181
Types.DATE, 'Start date of current HPV infection'),
182182
'hp_duration_hr1': Property(
183-
Types.INT,'Duration for current hr1 infection'),
183+
Types.INT, 'Duration for current hr1 infection'),
184184
'hp_duration_hr2': Property(
185-
Types.INT,'Duration for current hr2 infection'),
185+
Types.INT, 'Duration for current hr2 infection'),
186186
'hp_duration_hr3': Property(
187-
Types.INT,'Duration for current hr3 infection'),
187+
Types.INT, 'Duration for current hr3 infection'),
188188
'hp_duration_all_clear': Property(
189189
Types.INT, 'Duration for current all HPV infection'),
190190
# 'hp_date_clear_hr1': Property(
@@ -302,7 +302,7 @@ def _get_age_group_series(self, ages):
302302
labels=self.AGE_LABELS,
303303
right=False # right side not included
304304
)
305-
def _get_age_group(self,age_years):
305+
def _get_age_group(self, age_years):
306306
for i in range(len(self.AGE_BINS)-1):
307307
if self.AGE_BINS[i] <= age_years < self.AGE_BINS[i + 1]:
308308
return self.AGE_LABELS[i]
@@ -606,14 +606,13 @@ def report_daly_values(self):
606606
return health_values # returns the series
607607

608608
def report_summary_stats(self):
609-
def report_summary_stats(self):
610-
df = self.sim.population.props
611-
summary = {
612-
'infected_any': get_counts_by_sex_and_age_group(df, 'hp_is_infected')}
609+
df = self.sim.population.props
610+
summary = {
611+
'infected_any': get_counts_by_sex_and_age_group(df, 'hp_is_infected')}
613612

614-
for group in self.HPV_GROUPS:
615-
summary[f'infected_{group}'] = get_counts_by_sex_and_age_group(df, f'hp_infected_{group}')
616-
summary[f'persistent_{group}'] = get_counts_by_sex_and_age_group(df, f'hp_persistent_{group}')
613+
for group in self.HPV_GROUPS:
614+
summary[f'infected_{group}'] = get_counts_by_sex_and_age_group(df, f'hp_infected_{group}')
615+
summary[f'persistent_{group}'] = get_counts_by_sex_and_age_group(df, f'hp_persistent_{group}')
617616

618617
return summary
619618

tests/test_docs_data/test_HPV.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import os
2+
from pathlib import Path
3+
4+
import pandas as pd
5+
import pytest
6+
7+
from tlo import Date, Simulation, logging
8+
from tlo.methods import (
9+
demography,
10+
enhanced_lifestyle,
11+
epi,
12+
healthburden,
13+
healthseekingbehaviour,
14+
healthsystem,
15+
hpv,
16+
hiv,
17+
simplified_births,
18+
symptommanager,
19+
)
20+
21+
try:
22+
resourcefilepath = Path(os.path.dirname(__file__)) / "../resources"
23+
except NameError:
24+
resourcefilepath = "resources"
25+
26+
def check_dtypes(simulation):
27+
df = simulation.population.props
28+
orig = simulation.population.new_row
29+
assert (df.dtypes == orig.dtypes).all()
30+
31+
log_config = {
32+
"filename": "hpv_test", # The name of the output file (a timestamp will be appended).
33+
"directory": "./outputs/", # The default output path is `./outputs`. Change it here, if necessary
34+
"custom_levels": { # Customise the output of specific loggers. They are applied in order:
35+
"*": logging.WARNING, # Asterisk matches all loggers - we set the default level to WARNING
36+
"tlo.methods.hpv": logging.INFO,
37+
"tlo.methods.healthsystem": logging.INFO,
38+
"tlo.methods.demography": logging.INFO
39+
}
40+
}
41+
42+
@pytest.fixture
43+
def sim(seed):
44+
start_date = Date(2010, 1, 1)
45+
sim = Simulation(start_date=start_date, seed=seed, log_config=None, resourcefilepath=resourcefilepath)
46+
47+
# Register the appropriate modules
48+
sim.register(
49+
demography.Demography(),
50+
simplified_births.SimplifiedBirths(),
51+
enhanced_lifestyle.Lifestyle(),
52+
symptommanager.SymptomManager(),
53+
healthseekingbehaviour.HealthSeekingBehaviour(),
54+
healthburden.HealthBurden(),
55+
healthsystem.HealthSystem(
56+
disable=True, # disables the health system constraints so all HSI events run
57+
),
58+
epi.Epi(),
59+
hpv.HPV(),
60+
hiv.Hiv
61+
)
62+
63+
return sim
64+
65+
66+
def test_single_person(sim):
67+
"""
68+
run sim for one person
69+
assign infection
70+
check symptoms scheduled
71+
check symptoms resolved correctly
72+
"""
73+
# set high death rate - change all symptom probabilities to 1
74+
sim.modules['HPV'].parameters["symptom_prob"]["probability"] = 1
75+
76+
sim.make_initial_population(n=1)
77+
df = sim.population.props
78+
person_id = 0
79+
df.at[person_id, "hp_is_infected"] = True
80+
81+
# HPV onset event
82+
inf_event = hpv.HPV(person_id=person_id, module=sim.modules['HPV'])
83+
inf_event.apply(person_id)
84+
assert not pd.isnull(df.at[person_id, "me_date_measles"])
85+
86+
# check measles symptom resolve event and death scheduled
87+
events_for_this_person = sim.find_events_for_person(person_id)
88+
assert len(events_for_this_person) > 0
89+
next_event_date, next_event_obj = events_for_this_person[0]
90+
assert isinstance(next_event_obj, (measles.MeaslesDeathEvent, measles.MeaslesSymptomResolveEvent))
91+
92+
93+
@pytest.mark.slow
94+
def test_measles_cases_and_hsi_occurring(sim):
95+
""" Run the measles module
96+
check dtypes consistency
97+
check infections occurring
98+
check measles onset event scheduled
99+
check symptoms assigned
100+
check treatments occurring
101+
"""
102+
103+
end_date = Date(2011, 12, 31)
104+
popsize = 1000
105+
106+
# set high transmission probability
107+
sim.modules['Measles'].parameters['beta_baseline'] = 1.0
108+
109+
# set high death rate and change all symptom probabilities to 1
110+
cfr = sim.modules['Measles'].parameters["case_fatality_rate"]
111+
sim.modules['Measles'].parameters["case_fatality_rate"] = {k: 1.0 for k, v in cfr.items()}
112+
sim.modules['Measles'].parameters["symptom_prob"]["probability"] = 1
113+
114+
# Make the population
115+
sim.make_initial_population(n=popsize)
116+
117+
# check data types
118+
check_dtypes(sim)
119+
sim.simulate(end_date=end_date)
120+
check_dtypes(sim)
121+
122+
df = sim.population.props
123+
124+
# check people getting measles
125+
assert df['me_has_measles'].values.sum() > 0 # current cases of measles
126+
127+
# check that everyone who is currently infected gets a measles onset or symptom resolve event
128+
# they can have multiple symptom resolve events scheduled (by symptom onset and by treatment)
129+
inf = df.loc[df.is_alive & df.me_has_measles].index.tolist()
130+
131+
for idx in inf:
132+
events_for_this_person = sim.find_events_for_person(idx)
133+
assert len(events_for_this_person) > 0
134+
# assert measles event in event list for this person
135+
assert "tlo.methods.measles" in str(events_for_this_person)
136+
# find the first measles event
137+
measles_event_date = [date for (date, event) in events_for_this_person if "tlo.methods.measles" in str(event)]
138+
assert measles_event_date[0] >= df.loc[idx, "me_date_measles"]
139+
140+
# check symptoms assigned
141+
# there is an incubation period, so infected people may not have rash immediately
142+
# if on treatment for measles, must have rash for diagnosis
143+
has_rash = sim.modules['SymptomManager'].who_has('rash')
144+
current_measles_tx = df.index[df.is_alive & df.me_has_measles & df.me_on_treatment]
145+
if current_measles_tx.any():
146+
assert set(current_measles_tx) <= set(has_rash)
147+
148+
# check if any measles deaths occurred
149+
assert df.cause_of_death.loc[~df.is_alive].str.startswith('Measles').any()
150+
151+
152+
@pytest.mark.slow
153+
def test_measles_zero_death_rate(sim):
154+
155+
end_date = Date(2010, 12, 31)
156+
popsize = 10_000
157+
158+
# set zero death rate
159+
cfr = sim.modules['Measles'].parameters["case_fatality_rate"]
160+
sim.modules['Measles'].parameters["case_fatality_rate"] = {k: 0.0 for k, v in cfr.items()}
161+
162+
sim.make_initial_population(n=popsize)
163+
sim.simulate(end_date=end_date)
164+
df = sim.population.props
165+
166+
# no symptoms should equal no treatment (unless other rash has prompted incorrect tx: unlikely)
167+
assert not (df.loc[df.is_alive, 'me_on_treatment']).all()
168+
169+
# check that there have been no deaths caused by measles
170+
assert not df.cause_of_death.loc[~df.is_alive].str.startswith('Measles').any()

0 commit comments

Comments
 (0)