diff --git a/resources/ResourceFile_HIV.xlsx b/resources/ResourceFile_HIV.xlsx index 1cdb865eb1..f76169e701 100644 --- a/resources/ResourceFile_HIV.xlsx +++ b/resources/ResourceFile_HIV.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:58978c108515c3762addd18824129b2654f241d94bcc778ab17b27d0d8250593 -size 160402 +oid sha256:913d736db7717519270d61824a8855cbfd4d6e61a73b7ce51e2c3b7915b011ff +size 161597 diff --git a/resources/ResourceFile_TB.xlsx b/resources/ResourceFile_TB.xlsx index e6c1bf80db..3dfc69cd81 100644 --- a/resources/ResourceFile_TB.xlsx +++ b/resources/ResourceFile_TB.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cb13e128d4bcb3b694def108c3bd61b16508b48e389c3e5cdf8155717aab9e9 -size 55662 +oid sha256:120d687122772909c267db41c933664ccc6247c8aef59d49532547c0c3791121 +size 55634 diff --git a/resources/malaria/ResourceFile_malaria.xlsx b/resources/malaria/ResourceFile_malaria.xlsx index 70902b7480..a6487e80ae 100644 --- a/resources/malaria/ResourceFile_malaria.xlsx +++ b/resources/malaria/ResourceFile_malaria.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6ba5849e265103ee799d1982325b6fed1ef4d3df559ffce9d6790395c201fcaf -size 67562 +oid sha256:e8157368754dae9ce692fbd10fecf1e598f37fb258292085c93e1c881dd47aa9 +size 69590 diff --git a/src/scripts/htm_scenario_analyses/analysis_htm_scaleup.py b/src/scripts/htm_scenario_analyses/analysis_htm_scaleup.py new file mode 100644 index 0000000000..a89231f670 --- /dev/null +++ b/src/scripts/htm_scenario_analyses/analysis_htm_scaleup.py @@ -0,0 +1,112 @@ + +""" +This scenario file sets up the scenarios for simulating the effects of scaling up programs + +The scenarios are: +*0 baseline mode 1 +*1 scale-up HIV program +*2 scale-up TB program +*3 scale-up malaria program +*4 scale-up HIV and Tb and malaria programs + +scale-up occurs on the default scale-up start date (01/01/2025: in parameters list of resourcefiles) + +For all scenarios, keep all default health system settings + +check the batch configuration gets generated without error: +tlo scenario-run --draw-only src/scripts/htm_scenario_analyses/analysis_htm_scaleup.py + +Run on the batch system using: +tlo batch-submit src/scripts/htm_scenario_analyses/analysis_htm_scaleup.py + +or locally using: +tlo scenario-run src/scripts/htm_scenario_analyses/analysis_htm_scaleup.py + +or execute a single run: +tlo scenario-run src/scripts/htm_scenario_analyses/analysis_htm_scaleup.py --draw 1 0 + +""" + +from pathlib import Path + +from tlo import Date, logging +from tlo.methods import ( + demography, + enhanced_lifestyle, + epi, + healthburden, + healthseekingbehaviour, + healthsystem, + hiv, + malaria, + simplified_births, + symptommanager, + tb, +) +from tlo.scenario import BaseScenario + + +class EffectOfProgrammes(BaseScenario): + def __init__(self): + super().__init__() + self.seed = 0 + self.start_date = Date(2010, 1, 1) + self.end_date = Date(2020, 1, 1) + self.pop_size = 75_000 + self.number_of_draws = 5 + self.runs_per_draw = 1 + + def log_configuration(self): + return { + 'filename': 'scaleup_tests', + 'directory': Path('./outputs'), # <- (specified only for local running) + 'custom_levels': { + '*': logging.WARNING, + 'tlo.methods.hiv': logging.INFO, + 'tlo.methods.tb': logging.INFO, + 'tlo.methods.malaria': logging.INFO, + 'tlo.methods.demography': logging.INFO, + } + } + + def modules(self): + + return [ + demography.Demography(resourcefilepath=self.resources), + simplified_births.SimplifiedBirths(resourcefilepath=self.resources), + enhanced_lifestyle.Lifestyle(resourcefilepath=self.resources), + healthsystem.HealthSystem(resourcefilepath=self.resources), + symptommanager.SymptomManager(resourcefilepath=self.resources), + healthseekingbehaviour.HealthSeekingBehaviour(resourcefilepath=self.resources), + healthburden.HealthBurden(resourcefilepath=self.resources), + epi.Epi(resourcefilepath=self.resources), + hiv.Hiv(resourcefilepath=self.resources), + tb.Tb(resourcefilepath=self.resources), + malaria.Malaria(resourcefilepath=self.resources), + ] + + def draw_parameters(self, draw_number, rng): + scaleup_start_year = 2012 + + return { + 'Hiv': { + 'do_scaleup': [False, True, False, False, True][draw_number], + 'scaleup_start_year': scaleup_start_year + }, + 'Tb': { + 'do_scaleup': [False, False, True, False, True][draw_number], + 'scaleup_start_year': scaleup_start_year + }, + 'Malaria': { + 'do_scaleup': [False, False, False, True, True][draw_number], + 'scaleup_start_year': scaleup_start_year + }, + } + + +if __name__ == '__main__': + from tlo.cli import scenario_run + + scenario_run([__file__]) + + diff --git a/src/scripts/htm_scenario_analyses/scenario_plots.py b/src/scripts/htm_scenario_analyses/scenario_plots.py new file mode 100644 index 0000000000..d14454ae13 --- /dev/null +++ b/src/scripts/htm_scenario_analyses/scenario_plots.py @@ -0,0 +1,139 @@ +""" this reads in the outputs generates through analysis_htm_scaleup.py +and produces plots for HIV, TB and malaria incidence +""" + + +import datetime +from pathlib import Path + +import matplotlib.pyplot as plt +import pandas as pd +import seaborn as sns + +from tlo import Date +from tlo.analysis.utils import ( + extract_params, + extract_results, + get_scenario_info, + get_scenario_outputs, + load_pickled_dataframes, +) + +resourcefilepath = Path("./resources") +datestamp = datetime.date.today().strftime("__%Y_%m_%d") + +outputspath = Path("./outputs") + + +# 0) Find results_folder associated with a given batch_file (and get most recent [-1]) +results_folder = get_scenario_outputs("scaleup_tests", outputspath)[-1] + +# Declare path for output graphs from this script +make_graph_file_name = lambda stub: results_folder / f"{stub}.png" # noqa: E731 + +# look at one log (so can decide what to extract) +log = load_pickled_dataframes(results_folder) + +# get basic information about the results +info = get_scenario_info(results_folder) + +# 1) Extract the parameters that have varied over the set of simulations +params = extract_params(results_folder) + + +# DEATHS + + +def get_num_deaths_by_cause_label(_df): + """Return total number of Deaths by label within the TARGET_PERIOD + values are summed for all ages + df returned: rows=COD, columns=draw + """ + return _df \ + .loc[pd.to_datetime(_df.date).between(*TARGET_PERIOD)] \ + .groupby(_df['label']) \ + .size() + + +TARGET_PERIOD = (Date(2015, 1, 1), Date(2020, 1, 1)) + +num_deaths_by_cause_label = extract_results( + results_folder, + module='tlo.methods.demography', + key='death', + custom_generate_series=get_num_deaths_by_cause_label, + do_scaling=True + ) + + +def summarise_deaths_for_one_cause(results_folder, label): + """ returns mean deaths for each year of the simulation + values are aggregated across the runs of each draw + for the specified cause + """ + + results_deaths = extract_results( + results_folder, + module="tlo.methods.demography", + key="death", + custom_generate_series=( + lambda df: df.assign(year=df["date"].dt.year).groupby( + ["year", "label"])["person_id"].count() + ), + do_scaling=True, + ) + # removes multi-index + results_deaths = results_deaths.reset_index() + + # select only cause specified + tmp = results_deaths.loc[ + (results_deaths.label == label) + ] + + # group deaths by year + tmp = pd.DataFrame(tmp.groupby(["year"]).sum()) + + # get mean for each draw + mean_deaths = pd.concat({'mean': tmp.iloc[:, 1:].groupby(level=0, axis=1).mean()}, axis=1).swaplevel(axis=1) + + return mean_deaths + + +aids_deaths = summarise_deaths_for_one_cause(results_folder, 'AIDS') +tb_deaths = summarise_deaths_for_one_cause(results_folder, 'TB (non-AIDS)') +malaria_deaths = summarise_deaths_for_one_cause(results_folder, 'Malaria') + +draw_labels = ['No scale-up', 'HIV, scale-up', 'TB scale-up', 'Malaria scale-up'] + +colors = sns.color_palette("Set1", 4) # Blue, Orange, Green, Red + + +# Create subplots +fig, axs = plt.subplots(3, 1, figsize=(6, 10)) + +# Plot for df1 +for i, col in enumerate(aids_deaths.columns): + axs[0].plot(aids_deaths.index, aids_deaths[col], label=draw_labels[i], color=colors[i]) +axs[0].set_title('HIV/AIDS') +axs[0].legend() +axs[0].axvline(x=2015, color='gray', linestyle='--') + +# Plot for df2 +for i, col in enumerate(tb_deaths.columns): + axs[1].plot(tb_deaths.index, tb_deaths[col], color=colors[i]) +axs[1].set_title('TB') +axs[1].axvline(x=2015, color='gray', linestyle='--') + +# Plot for df3 +for i, col in enumerate(malaria_deaths.columns): + axs[2].plot(malaria_deaths.index, malaria_deaths[col], color=colors[i]) +axs[2].set_title('Malaria') +axs[2].axvline(x=2015, color='gray', linestyle='--') + +for ax in axs: + ax.set_xlabel('Years') + ax.set_ylabel('Number deaths') + +plt.tight_layout() +plt.show() + diff --git a/src/scripts/malaria/analysis_malaria.py b/src/scripts/malaria/analysis_malaria.py index 56d05cf3ae..b2b4217dc6 100644 --- a/src/scripts/malaria/analysis_malaria.py +++ b/src/scripts/malaria/analysis_malaria.py @@ -34,8 +34,8 @@ resourcefilepath = Path("./resources") start_date = Date(2010, 1, 1) -end_date = Date(2016, 1, 1) -popsize = 300 +end_date = Date(2014, 1, 1) +popsize = 100 # set up the log config @@ -84,6 +84,15 @@ ) ) +# update parameters +sim.modules["Hiv"].parameters["do_scaleup"] = True +sim.modules["Tb"].parameters["do_scaleup"] = True +sim.modules["Malaria"].parameters["do_scaleup"] = True +sim.modules["Hiv"].parameters["scaleup_start"] = 2 +sim.modules["Tb"].parameters["scaleup_start"] = 2 +sim.modules["Malaria"].parameters["scaleup_start"] = 2 + + # Run the simulation and flush the logger sim.make_initial_population(n=popsize) sim.simulate(end_date=end_date) @@ -97,5 +106,5 @@ pickle.dump(dict(output), f, pickle.HIGHEST_PROTOCOL) # load the results -with open(outputpath / "default_run.pickle", "rb") as f: +with open(outputpath / "malaria_run.pickle", "rb") as f: output = pickle.load(f) diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index d86c706217..1ddafe4c47 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -31,7 +31,7 @@ import numpy as np import pandas as pd -from tlo import DAYS_IN_YEAR, DateOffset, Module, Parameter, Property, Types, logging +from tlo import DAYS_IN_YEAR, Date, DateOffset, Module, Parameter, Property, Types, logging from tlo.events import Event, IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor from tlo.methods import Metadata, demography, tb @@ -397,6 +397,19 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): "length in days of inpatient stay for end-of-life HIV patients: list has two elements [low-bound-inclusive," " high-bound-exclusive]", ), + # ------------------ scale-up parameters for scenario analysis ------------------ # + "do_scaleup": Parameter( + Types.BOOL, + "argument to determine whether scale-up of program will be implemented" + ), + "scaleup_start_year": Parameter( + Types.INT, + "the year when the scale-up starts (it will occur on 1st January of that year)" + ), + "scaleup_parameters": Parameter( + Types.DICT, + "the parameters and values changed in scenario analysis" + ), } def read_parameters(self, data_folder): @@ -434,6 +447,9 @@ def read_parameters(self, data_folder): # Load spectrum estimates of treatment cascade p["treatment_cascade"] = workbook["spectrum_treatment_cascade"] + # load parameters for scale-up projections + p["scaleup_parameters"] = workbook["scaleup_parameters"].set_index('parameter')['scaleup_value'].to_dict() + # DALY weights # get the DALY weight that this module will use from the weight database (these codes are just random!) if "HealthBurden" in self.sim.modules.keys(): @@ -894,6 +910,12 @@ def initialise_simulation(self, sim): # 2) Schedule the Logging Event sim.schedule_event(HivLoggingEvent(self), sim.date + DateOffset(years=1)) + # Optional: Schedule the scale-up of programs + if self.parameters["do_scaleup"]: + scaleup_start_date = Date(self.parameters["scaleup_start_year"], 1, 1) + assert scaleup_start_date >= self.sim.start_date, f"Date {scaleup_start_date} is before simulation starts." + sim.schedule_event(HivScaleUpEvent(self), scaleup_start_date) + # 3) Determine who has AIDS and impose the Symptoms 'aids_symptoms' # Those on ART currently (will not get any further events scheduled): @@ -1076,6 +1098,44 @@ def initialise_simulation(self, sim): ) ) + def update_parameters_for_program_scaleup(self): + + p = self.parameters + scaled_params = p["scaleup_parameters"] + + if p["do_scaleup"]: + + # scale-up HIV program + # reduce risk of HIV - applies to whole adult population + p["beta"] = p["beta"] * scaled_params["reduction_in_hiv_beta"] + + # increase PrEP coverage for FSW after HIV test + p["prob_prep_for_fsw_after_hiv_test"] = scaled_params["prob_prep_for_fsw_after_hiv_test"] + + # prep poll for AGYW - target to the highest risk + # increase retention to 75% for FSW and AGYW + p["prob_prep_for_agyw"] = scaled_params["prob_prep_for_agyw"] + p["probability_of_being_retained_on_prep_every_3_months"] = scaled_params["probability_of_being_retained_on_prep_every_3_months"] + + # increase probability of VMMC after hiv test + p["prob_circ_after_hiv_test"] = scaled_params["prob_circ_after_hiv_test"] + + # increase testing/diagnosis rates, default 2020 0.03/0.25 -> 93% dx + p["hiv_testing_rates"]["annual_testing_rate_children"] = scaled_params["annual_testing_rate_children"] + p["hiv_testing_rates"]["annual_testing_rate_adults"] = scaled_params["annual_testing_rate_adults"] + + # ANC testing - value for mothers and infants testing + p["prob_hiv_test_at_anc_or_delivery"] = scaled_params["prob_hiv_test_at_anc_or_delivery"] + p["prob_hiv_test_for_newborn_infant"] = scaled_params["prob_hiv_test_for_newborn_infant"] + + # prob ART start if dx, this is already 95% at 2020 + p["prob_start_art_after_hiv_test"] = scaled_params["prob_start_art_after_hiv_test"] + + # viral suppression rates + # adults already at 95% by 2020 + # change all column values + p["prob_start_art_or_vs"]["virally_suppressed_on_art"] = scaled_params["virally_suppressed_on_art"] + def on_birth(self, mother_id, child_id): """ * Initialise our properties for a newborn individual; @@ -2214,6 +2274,20 @@ def apply(self, person_id): ) +class HivScaleUpEvent(Event, PopulationScopeEventMixin): + """ This event exists to change parameters or functions + depending on the scenario for projections which has been set + It only occurs once on date: scaleup_start_date, + called by initialise_simulation + """ + + def __init__(self, module): + super().__init__(module) + + def apply(self, population): + self.module.update_parameters_for_program_scaleup() + + # --------------------------------------------------------------------------- # Health System Interactions (HSI) # --------------------------------------------------------------------------- diff --git a/src/tlo/methods/malaria.py b/src/tlo/methods/malaria.py index bf7b5a11be..f322783717 100644 --- a/src/tlo/methods/malaria.py +++ b/src/tlo/methods/malaria.py @@ -11,7 +11,7 @@ import pandas as pd -from tlo import DateOffset, Module, Parameter, Property, Types, logging +from tlo import Date, DateOffset, Module, Parameter, Property, Types, logging from tlo.events import Event, IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, Predictor from tlo.methods import Metadata @@ -188,8 +188,20 @@ def __init__(self, name=None, resourcefilepath=None): 'prob_of_treatment_success': Parameter( Types.REAL, 'probability that treatment will clear malaria symptoms' + ), + # ------------------ scale-up parameters for scenario analysis ------------------ # + "do_scaleup": Parameter( + Types.BOOL, + "argument to determine whether scale-up of program will be implemented" + ), + "scaleup_start_year": Parameter( + Types.INT, + "the year when the scale-up starts (it will occur on 1st January of that year)" + ), + "scaleup_parameters": Parameter( + Types.DICT, + "the parameters and values changed in scenario analysis" ) - } PROPERTIES = { @@ -242,11 +254,15 @@ def read_parameters(self, data_folder): p['sev_symp_prob'] = workbook['severe_symptoms'] p['rdt_testing_rates'] = workbook['WHO_TestData2023'] + p['highrisk_districts'] = workbook['highrisk_districts'] p['inf_inc'] = pd.read_csv(self.resourcefilepath / 'malaria' / 'ResourceFile_malaria_InfInc_expanded.csv') p['clin_inc'] = pd.read_csv(self.resourcefilepath / 'malaria' / 'ResourceFile_malaria_ClinInc_expanded.csv') p['sev_inc'] = pd.read_csv(self.resourcefilepath / 'malaria' / 'ResourceFile_malaria_SevInc_expanded.csv') + # load parameters for scale-up projections + p["scaleup_parameters"] = workbook["scaleup_parameters"].set_index('parameter')['scaleup_value'].to_dict() + # check itn projected values are <=0.7 and rounded to 1dp for matching to incidence tables p['itn'] = round(p['itn'], 1) assert (p['itn'] <= 0.7) @@ -356,7 +372,7 @@ def pre_initialise_population(self): p['rr_severe_malaria_hiv_over5']), Predictor().when('(hv_inf == True) & (is_pregnant == True)', p['rr_severe_malaria_hiv_pregnant']), - ] if "hiv" in self.sim.modules else [] + ] if "Hiv" in self.sim.modules else [] self.lm["rr_of_severe_malaria"] = LinearModel.multiplicative( *(predictors + conditional_predictors)) @@ -534,8 +550,12 @@ def general_population_rdt_scheduler(self, population): # extract annual testing rates from NMCP reports # this is the # rdts issued divided by population size - test_rates = p['rdt_testing_rates'].set_index('Year')['Rate_rdt_testing'].dropna() - rdt_rate = test_rates.loc[min(test_rates.index.max(), self.sim.date.year)] / 12 + year = self.sim.date.year if self.sim.date.year <= 2024 else 2024 + + test_rates = ( + p['rdt_testing_rates'].set_index('Year')['Rate_rdt_testing'].dropna() + ) + rdt_rate = test_rates.loc[min(test_rates.index.max(), year)] / 12 # adjust rdt usage reported rate to reflect consumables availability rdt_rate = rdt_rate * p['scaling_factor_for_rdt_availability'] @@ -578,6 +598,12 @@ def initialise_simulation(self, sim): sim.schedule_event(MalariaTxLoggingEvent(self), sim.date + DateOffset(years=1)) sim.schedule_event(MalariaPrevDistrictLoggingEvent(self), sim.date + DateOffset(months=1)) + # Optional: Schedule the scale-up of programs + if self.parameters["do_scaleup"]: + scaleup_start_date = Date(self.parameters["scaleup_start_year"], 1, 1) + assert scaleup_start_date >= self.sim.start_date, f"Date {scaleup_start_date} is before simulation starts." + sim.schedule_event(MalariaScaleUpEvent(self), scaleup_start_date) + # 2) ----------------------------------- DIAGNOSTIC TESTS ----------------------------------- # Create the diagnostic test representing the use of RDT for malaria diagnosis # and registers it with the Diagnostic Test Manager @@ -626,7 +652,56 @@ def initialise_simulation(self, sim): # malaria IPTp for pregnant women self.item_codes_for_consumables_required['malaria_iptp'] = get_item_code( - 'Sulfamethoxazole + trimethropin, tablet 400 mg + 80 mg') + 'Sulfamethoxazole + trimethropin, tablet 400 mg + 80 mg' + ) + + def update_parameters_for_program_scaleup(self): + + p = self.parameters + scaled_params = p["scaleup_parameters"] + + if p["do_scaleup"]: + + # scale-up malaria program + # increase testing + # prob_malaria_case_tests=0.4 default + p["prob_malaria_case_tests"] = scaled_params["prob_malaria_case_tests"] + + # gen pop testing rates + # annual Rate_rdt_testing=0.64 at 2023 + p["rdt_testing_rates"]["Rate_rdt_testing"] = scaled_params["rdt_testing_rates"] + + # treatment reaches XX + # no default between testing and treatment, governed by tx availability + + # coverage IPTp reaches XX + # given during ANC visits and MalariaIPTp Event which selects ALL eligible women + + # treatment success reaches 1 - default is currently 1 also + p["prob_of_treatment_success"] = scaled_params["prob_of_treatment_success"] + + # bednet and ITN coverage + # set IRS for 4 high-risk districts + # lookup table created in malaria read_parameters + # produces self.itn_irs called by malaria poll to draw incidence + # need to overwrite this + highrisk_distr_num = p["highrisk_districts"]["district_num"] + + # Find indices where District_Num is in highrisk_distr_num + mask = self.itn_irs['irs_rate'].index.get_level_values('District_Num').isin( + highrisk_distr_num) + + # IRS values can be 0 or 0.8 - no other value in lookup table + self.itn_irs['irs_rate'].loc[mask] = scaled_params["irs_district"] + + # set ITN for all districts + # Set these values to 0.7 - this is the max value possible in lookup table + # equivalent to 0.7 of all pop sleeping under bednet + # household coverage could be 100%, but not everyone in household sleeping under bednet + self.itn_irs['itn_rate'] = scaled_params["itn_district"] + + # itn rates for 2019 onwards + p["itn"] = scaled_params["itn"] def on_birth(self, mother_id, child_id): df = self.sim.population.props @@ -819,6 +894,21 @@ def apply(self, population): self.module.general_population_rdt_scheduler(population) +class MalariaScaleUpEvent(Event, PopulationScopeEventMixin): + """ This event exists to change parameters or functions + depending on the scenario for projections which has been set + It only occurs once on date: scaleup_start_date, + called by initialise_simulation + """ + + def __init__(self, module): + super().__init__(module) + + def apply(self, population): + + self.module.update_parameters_for_program_scaleup() + + class MalariaIPTp(RegularEvent, PopulationScopeEventMixin): """ malaria prophylaxis for pregnant women diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 02d860fe52..aa62f3ea8a 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -9,7 +9,7 @@ import pandas as pd -from tlo import DateOffset, Module, Parameter, Property, Types, logging +from tlo import Date, DateOffset, Module, Parameter, Property, Types, logging from tlo.events import Event, IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor from tlo.methods import Metadata, hiv @@ -376,6 +376,19 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): Types.LIST, "length of inpatient stay for end-of-life TB patients", ), + # ------------------ scale-up parameters for scenario analysis ------------------ # + "do_scaleup": Parameter( + Types.BOOL, + "argument to determine whether scale-up of program will be implemented" + ), + "scaleup_start_year": Parameter( + Types.INT, + "the year when the scale-up starts (it will occur on 1st January of that year)" + ), + "scaleup_parameters": Parameter( + Types.DICT, + "the parameters and values changed in scenario analysis" + ) } def read_parameters(self, data_folder): @@ -413,6 +426,9 @@ def read_parameters(self, data_folder): .tolist() ) + # load parameters for scale-up projections + p["scaleup_parameters"] = workbook["scaleup_parameters"].set_index('parameter')['scaleup_value'].to_dict() + # 2) Get the DALY weights if "HealthBurden" in self.sim.modules.keys(): # HIV-negative @@ -849,6 +865,13 @@ def initialise_simulation(self, sim): sim.schedule_event(TbSelfCureEvent(self), sim.date) sim.schedule_event(TbActiveCasePoll(self), sim.date + DateOffset(years=1)) + # 2) log at the end of the year + # Optional: Schedule the scale-up of programs + if self.parameters["do_scaleup"]: + scaleup_start_date = Date(self.parameters["scaleup_start_year"], 1, 1) + assert scaleup_start_date >= self.sim.start_date, f"Date {scaleup_start_date} is before simulation starts." + sim.schedule_event(TbScaleUpEvent(self), scaleup_start_date) + # 2) log at the end of the year sim.schedule_event(TbLoggingEvent(self), sim.date + DateOffset(years=1)) @@ -861,6 +884,31 @@ def initialise_simulation(self, sim): TbCheckPropertiesEvent(self), sim.date + pd.DateOffset(months=1) ) + def update_parameters_for_program_scaleup(self): + + p = self.parameters + scaled_params = p["scaleup_parameters"] + + if p["do_scaleup"]: + + # scale-up TB program + # use NTP treatment rates + p["rate_testing_active_tb"]["treatment_coverage"] = scaled_params["tb_treatment_coverage"] + + # increase tb treatment success rates + p["prob_tx_success_ds"] = scaled_params["tb_prob_tx_success_ds"] + p["prob_tx_success_mdr"] = scaled_params["tb_prob_tx_success_mdr"] + p["prob_tx_success_0_4"] = scaled_params["tb_prob_tx_success_0_4"] + p["prob_tx_success_5_14"] = scaled_params["tb_prob_tx_success_5_14"] + + # change first-line testing for TB to xpert + p["first_line_test"] = scaled_params["first_line_test"] + p["second_line_test"] = scaled_params["second_line_test"] + + # increase coverage of IPT + p["ipt_coverage"]["coverage_plhiv"] = scaled_params["ipt_coverage_plhiv"] + p["ipt_coverage"]["coverage_paediatric"] = scaled_params["ipt_coverage_paediatric"] + def on_birth(self, mother_id, child_id): """Initialise properties for a newborn individual allocate IPT for child if mother diagnosed with TB @@ -1367,6 +1415,21 @@ def apply(self, population): self.module.relapse_event(population) +class TbScaleUpEvent(Event, PopulationScopeEventMixin): + """ This event exists to change parameters or functions + depending on the scenario for projections which has been set + It only occurs once on date: scaleup_start_date, + called by initialise_simulation + """ + + def __init__(self, module): + super().__init__(module) + + def apply(self, population): + + self.module.update_parameters_for_program_scaleup() + + class TbActiveEvent(RegularEvent, PopulationScopeEventMixin): """ * check for those with dates of active tb onset within last time-period diff --git a/tests/test_htm_scaleup.py b/tests/test_htm_scaleup.py new file mode 100644 index 0000000000..dbb2638c88 --- /dev/null +++ b/tests/test_htm_scaleup.py @@ -0,0 +1,210 @@ +""" Tests for setting up the HIV, TB and malaria scenarios used for projections """ + +import os +from pathlib import Path + +import pandas as pd + +from tlo import Date, Simulation +from tlo.methods import ( + demography, + enhanced_lifestyle, + epi, + healthburden, + healthseekingbehaviour, + healthsystem, + hiv, + malaria, + simplified_births, + symptommanager, + tb, +) + +resourcefilepath = Path(os.path.dirname(__file__)) / "../resources" + +start_date = Date(2010, 1, 1) +scaleup_start_year = 2012 # <-- the scale-up will occur on 1st January of that year +end_date = Date(2013, 1, 1) + + +def get_sim(seed): + """ + register all necessary modules for the tests to run + """ + + sim = Simulation(start_date=start_date, seed=seed) + + # Register the appropriate modules + sim.register( + demography.Demography(resourcefilepath=resourcefilepath), + simplified_births.SimplifiedBirths(resourcefilepath=resourcefilepath), + enhanced_lifestyle.Lifestyle(resourcefilepath=resourcefilepath), + healthsystem.HealthSystem(resourcefilepath=resourcefilepath), + symptommanager.SymptomManager(resourcefilepath=resourcefilepath), + healthseekingbehaviour.HealthSeekingBehaviour(resourcefilepath=resourcefilepath), + healthburden.HealthBurden(resourcefilepath=resourcefilepath), + epi.Epi(resourcefilepath=resourcefilepath), + hiv.Hiv(resourcefilepath=resourcefilepath), + tb.Tb(resourcefilepath=resourcefilepath), + malaria.Malaria(resourcefilepath=resourcefilepath), + ) + + return sim + + +def check_initial_params(sim): + + original_params = pd.read_excel(resourcefilepath / 'ResourceFile_HIV.xlsx', sheet_name='parameters') + + # check initial parameters + assert sim.modules["Hiv"].parameters["beta"] == \ + original_params.loc[original_params.parameter_name == "beta", "value"].values[0] + assert sim.modules["Hiv"].parameters["prob_prep_for_fsw_after_hiv_test"] == original_params.loc[ + original_params.parameter_name == "prob_prep_for_fsw_after_hiv_test", "value"].values[0] + assert sim.modules["Hiv"].parameters["prob_prep_for_agyw"] == original_params.loc[ + original_params.parameter_name == "prob_prep_for_agyw", "value"].values[0] + assert sim.modules["Hiv"].parameters["probability_of_being_retained_on_prep_every_3_months"] == original_params.loc[ + original_params.parameter_name == "probability_of_being_retained_on_prep_every_3_months", "value"].values[0] + assert sim.modules["Hiv"].parameters["prob_circ_after_hiv_test"] == original_params.loc[ + original_params.parameter_name == "prob_circ_after_hiv_test", "value"].values[0] + + +def test_hiv_scale_up(seed): + """ test hiv program scale-up changes parameters correctly + and on correct date """ + + original_params = pd.read_excel(resourcefilepath / 'ResourceFile_HIV.xlsx', sheet_name="parameters") + new_params = pd.read_excel(resourcefilepath / 'ResourceFile_HIV.xlsx', sheet_name="scaleup_parameters") + + popsize = 100 + + sim = get_sim(seed=seed) + + # check initial parameters + check_initial_params(sim) + + # update parameters to instruct there to be a scale-up + sim.modules["Hiv"].parameters["do_scaleup"] = True + sim.modules["Hiv"].parameters["scaleup_start_year"] = scaleup_start_year + + # Make the population + sim.make_initial_population(n=popsize) + sim.simulate(end_date=end_date) + + # check HIV parameters changed + assert sim.modules["Hiv"].parameters["beta"] < original_params.loc[ + original_params.parameter_name == "beta", "value"].values[0] + assert sim.modules["Hiv"].parameters["prob_prep_for_fsw_after_hiv_test"] == new_params.loc[ + new_params.parameter == "prob_prep_for_fsw_after_hiv_test", "scaleup_value"].values[0] + assert sim.modules["Hiv"].parameters["prob_prep_for_agyw"] == new_params.loc[ + new_params.parameter == "prob_prep_for_agyw", "scaleup_value"].values[0] + assert sim.modules["Hiv"].parameters["probability_of_being_retained_on_prep_every_3_months"] == new_params.loc[ + new_params.parameter == "probability_of_being_retained_on_prep_every_3_months", "scaleup_value"].values[0] + assert sim.modules["Hiv"].parameters["prob_circ_after_hiv_test"] == new_params.loc[ + new_params.parameter == "prob_circ_after_hiv_test", "scaleup_value"].values[0] + + # check malaria parameters unchanged + mal_original_params = pd.read_excel(resourcefilepath / 'malaria' / 'ResourceFile_malaria.xlsx', + sheet_name="parameters") + mal_rdt_testing = pd.read_excel(resourcefilepath / 'malaria' / 'ResourceFile_malaria.xlsx', + sheet_name="WHO_TestData2023") + + assert sim.modules["Malaria"].parameters["prob_malaria_case_tests"] == mal_original_params.loc[ + mal_original_params.parameter_name == "prob_malaria_case_tests", "value"].values[0] + pd.testing.assert_series_equal(sim.modules["Malaria"].parameters["rdt_testing_rates"]["Rate_rdt_testing"], + mal_rdt_testing["Rate_rdt_testing"]) + + # all irs coverage levels should be < 1.0 + assert sim.modules["Malaria"].itn_irs['irs_rate'].all() < 1.0 + # itn rates for 2019 onwards + assert sim.modules["Malaria"].parameters["itn"] == mal_original_params.loc[ + mal_original_params.parameter_name == "itn", "value"].values[0] + + # check tb parameters unchanged + tb_original_params = pd.read_excel(resourcefilepath / 'ResourceFile_TB.xlsx', sheet_name="parameters") + tb_testing = pd.read_excel(resourcefilepath / 'ResourceFile_TB.xlsx', sheet_name="NTP2019") + + pd.testing.assert_series_equal(sim.modules["Tb"].parameters["rate_testing_active_tb"]["treatment_coverage"], + tb_testing["treatment_coverage"]) + assert sim.modules["Tb"].parameters["prob_tx_success_ds"] == tb_original_params.loc[ + tb_original_params.parameter_name == "prob_tx_success_ds", "value"].values[0] + assert sim.modules["Tb"].parameters["prob_tx_success_mdr"] == tb_original_params.loc[ + tb_original_params.parameter_name == "prob_tx_success_mdr", "value"].values[0] + assert sim.modules["Tb"].parameters["prob_tx_success_0_4"] == tb_original_params.loc[ + tb_original_params.parameter_name == "prob_tx_success_0_4", "value"].values[0] + assert sim.modules["Tb"].parameters["prob_tx_success_5_14"] == tb_original_params.loc[ + tb_original_params.parameter_name == "prob_tx_success_5_14", "value"].values[0] + assert sim.modules["Tb"].parameters["first_line_test"] == tb_original_params.loc[ + tb_original_params.parameter_name == "first_line_test", "value"].values[0] + + +def test_htm_scale_up(seed): + """ test hiv/tb/malaria program scale-up changes parameters correctly + and on correct date """ + + # Load data on HIV prevalence + original_hiv_params = pd.read_excel(resourcefilepath / 'ResourceFile_HIV.xlsx', sheet_name="parameters") + new_hiv_params = pd.read_excel(resourcefilepath / 'ResourceFile_HIV.xlsx', sheet_name="scaleup_parameters") + + popsize = 100 + + sim = get_sim(seed=seed) + + # check initial parameters + check_initial_params(sim) + + # update parameters + sim.modules["Hiv"].parameters["do_scaleup"] = True + sim.modules["Hiv"].parameters["scaleup_start_year"] = scaleup_start_year + sim.modules["Tb"].parameters["do_scaleup"] = True + sim.modules["Tb"].parameters["scaleup_start_year"] = scaleup_start_year + sim.modules["Malaria"].parameters["do_scaleup"] = True + sim.modules["Malaria"].parameters["scaleup_start_year"] = scaleup_start_year + + # Make the population + sim.make_initial_population(n=popsize) + sim.simulate(end_date=end_date) + + # check HIV parameters changed + assert sim.modules["Hiv"].parameters["beta"] < original_hiv_params.loc[ + original_hiv_params.parameter_name == "beta", "value"].values[0] + assert sim.modules["Hiv"].parameters["prob_prep_for_fsw_after_hiv_test"] == new_hiv_params.loc[ + new_hiv_params.parameter == "prob_prep_for_fsw_after_hiv_test", "scaleup_value"].values[0] + assert sim.modules["Hiv"].parameters["prob_prep_for_agyw"] == new_hiv_params.loc[ + new_hiv_params.parameter == "prob_prep_for_agyw", "scaleup_value"].values[0] + assert sim.modules["Hiv"].parameters["probability_of_being_retained_on_prep_every_3_months"] == new_hiv_params.loc[ + new_hiv_params.parameter == "probability_of_being_retained_on_prep_every_3_months", "scaleup_value"].values[0] + assert sim.modules["Hiv"].parameters["prob_circ_after_hiv_test"] == new_hiv_params.loc[ + new_hiv_params.parameter == "prob_circ_after_hiv_test", "scaleup_value"].values[0] + + # check malaria parameters changed + new_mal_params = pd.read_excel(resourcefilepath / 'malaria' / 'ResourceFile_malaria.xlsx', + sheet_name="scaleup_parameters") + + assert sim.modules["Malaria"].parameters["prob_malaria_case_tests"] == new_mal_params.loc[ + new_mal_params.parameter == "prob_malaria_case_tests", "scaleup_value"].values[0] + assert sim.modules["Malaria"].parameters["rdt_testing_rates"]["Rate_rdt_testing"].eq(new_mal_params.loc[ + new_mal_params.parameter == "rdt_testing_rates", "scaleup_value"].values[0]).all() + + # some irs coverage levels should now = 1.0 + assert sim.modules["Malaria"].itn_irs['irs_rate'].any() == 1.0 + # itn rates for 2019 onwards + assert sim.modules["Malaria"].parameters["itn"] == new_mal_params.loc[ + new_mal_params.parameter == "itn", "scaleup_value"].values[0] + + # check tb parameters changed + new_tb_params = pd.read_excel(resourcefilepath / 'ResourceFile_TB.xlsx', sheet_name="scaleup_parameters") + + assert sim.modules["Tb"].parameters["rate_testing_active_tb"]["treatment_coverage"].eq(new_tb_params.loc[ + new_tb_params.parameter == "tb_treatment_coverage", "scaleup_value"].values[0]).all() + assert sim.modules["Tb"].parameters["prob_tx_success_ds"] == new_tb_params.loc[ + new_tb_params.parameter == "tb_prob_tx_success_ds", "scaleup_value"].values[0] + assert sim.modules["Tb"].parameters["prob_tx_success_mdr"] == new_tb_params.loc[ + new_tb_params.parameter == "tb_prob_tx_success_mdr", "scaleup_value"].values[0] + assert sim.modules["Tb"].parameters["prob_tx_success_0_4"] == new_tb_params.loc[ + new_tb_params.parameter == "tb_prob_tx_success_0_4", "scaleup_value"].values[0] + assert sim.modules["Tb"].parameters["prob_tx_success_5_14"] == new_tb_params.loc[ + new_tb_params.parameter == "tb_prob_tx_success_5_14", "scaleup_value"].values[0] + assert sim.modules["Tb"].parameters["first_line_test"] == new_tb_params.loc[ + new_tb_params.parameter == "first_line_test", "scaleup_value"].values[0] +