diff --git a/resources/ResourceFile_LabourSkilledBirthAttendance.xlsx b/resources/ResourceFile_LabourSkilledBirthAttendance.xlsx index 9d61093db8..6ae417a123 100644 --- a/resources/ResourceFile_LabourSkilledBirthAttendance.xlsx +++ b/resources/ResourceFile_LabourSkilledBirthAttendance.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ebd3aff1d77254f7cd7bb1dc019e2e6a86700dcd6009cecd11a70da4b85a3c93 -size 31777 +oid sha256:4bc6e58d3ebfadb58552a58fe7a0592195d28cb7d29fcff3c7c2859f2f3ea559 +size 31686 diff --git a/resources/ResourceFile_PregnancyCohort.xlsx b/resources/ResourceFile_PregnancyCohort.xlsx new file mode 100644 index 0000000000..bd4b0af086 --- /dev/null +++ b/resources/ResourceFile_PregnancyCohort.xlsx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f41deeb8fd44fda6cc451a7e23390c98bba449e704ccd62e258caaf2e6e6606 +size 18595782 diff --git a/resources/ResourceFile_PregnancySupervisor.xlsx b/resources/ResourceFile_PregnancySupervisor.xlsx index 3ee3406d7a..cf2d7636cd 100644 --- a/resources/ResourceFile_PregnancySupervisor.xlsx +++ b/resources/ResourceFile_PregnancySupervisor.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbd0974016b374a3286e379ef3cb1f297307ef410880c3f902e9439194a6d37d -size 22225 +oid sha256:d05fc317eafb2761d03ec2107cf5ce5322fac006289ccb1c0366f10e3ab1f685 +size 23984 diff --git a/resources/maternal cohort/ResourceFile_All2024PregnanciesCohortModel.xlsx b/resources/maternal cohort/ResourceFile_All2024PregnanciesCohortModel.xlsx new file mode 100644 index 0000000000..500fcfd169 --- /dev/null +++ b/resources/maternal cohort/ResourceFile_All2024PregnanciesCohortModel.xlsx @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:39435a18741e06862f5c0b29d47c331c2c15d22f2954d204c5cf9e3fd80f20bc +size 20542399 diff --git a/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py b/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py new file mode 100644 index 0000000000..f17877c7a6 --- /dev/null +++ b/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py @@ -0,0 +1,58 @@ +import numpy as np +import pandas as pd + +from pathlib import Path + +from tlo import Date, logging +from tlo.methods import mnh_cohort_module +from tlo.methods.fullmodel import fullmodel +from tlo.scenario import BaseScenario + + +class BaselineScenario(BaseScenario): + def __init__(self): + super().__init__() + self.seed = 537184 + self.start_date = Date(2024, 1, 1) + self.end_date = Date(2025, 1, 2) + self.pop_size = 5000 + self.number_of_draws = 3 + self.runs_per_draw = 10 + + def log_configuration(self): + return { + 'filename': 'block_intervention_test', 'directory': './outputs', + "custom_levels": { + "*": logging.WARNING, + "tlo.methods.demography": logging.INFO, + "tlo.methods.demography.detail": logging.INFO, + "tlo.methods.contraception": logging.INFO, + "tlo.methods.healthsystem.summary": logging.INFO, + "tlo.methods.healthburden": logging.INFO, + "tlo.methods.labour": logging.INFO, + "tlo.methods.labour.detail": logging.INFO, + "tlo.methods.newborn_outcomes": logging.INFO, + "tlo.methods.care_of_women_during_pregnancy": logging.INFO, + "tlo.methods.pregnancy_supervisor": logging.INFO, + "tlo.methods.postnatal_supervisor": logging.INFO, + } + } + + def modules(self): + return [*fullmodel(resourcefilepath=self.resources, + module_kwargs={'Schisto': {'mda_execute': False}}), + mnh_cohort_module.MaternalNewbornHealthCohort(resourcefilepath=self.resources)] + + def draw_parameters(self, draw_number, rng): + interventions_for_analysis = ['blood_transfusion', 'pph_treatment_uterotonics', 'sepsis_treatment'] + + return {'PregnancySupervisor': { + 'analysis_year': 2024, + 'interventions_analysis': True, + 'interventions_under_analysis':[interventions_for_analysis[draw_number-1]], + 'intervention_analysis_availability': 0.0}} + + +if __name__ == '__main__': + from tlo.cli import scenario_run + scenario_run([__file__]) diff --git a/src/scripts/maternal_perinatal_analyses/cohort_analysis/dummy_cohort_azure_calib.py b/src/scripts/maternal_perinatal_analyses/cohort_analysis/dummy_cohort_azure_calib.py new file mode 100644 index 0000000000..711b48c7a0 --- /dev/null +++ b/src/scripts/maternal_perinatal_analyses/cohort_analysis/dummy_cohort_azure_calib.py @@ -0,0 +1,57 @@ +import os + +import pandas as pd + +from tlo.analysis.utils import extract_results, get_scenario_outputs, summarize + +outputspath = './outputs/sejjj49@ucl.ac.uk/' +scenario_filename = 'cohort_test-2024-10-09T130546Z' +# scenario_filename2 = 'cohort_test-2024-10-15T122825Z' +scenario_filename2 = 'cohort_test-2024-10-16T071357Z' + +results_folder_old = get_scenario_outputs(scenario_filename, outputspath)[-1] +results_folder_new = get_scenario_outputs(scenario_filename2, outputspath)[-1] + +def get_data_frames(key, results_folder): + def sort_df(_df): + _x = _df.drop(columns=['date'], inplace=False) + return _x.iloc[0] + + results_df = summarize (extract_results( + results_folder, + module="tlo.methods.pregnancy_supervisor", + key=key, + custom_generate_series=sort_df, + do_scaling=False + )) + + return results_df + +results_old = {k:get_data_frames(k, results_folder_old) for k in + ['mat_comp_incidence', 'nb_comp_incidence', 'deaths_and_stillbirths','service_coverage', + 'yearly_mnh_counter_dict']} + +results_new = {k:get_data_frames(k, results_folder_new) for k in + ['mat_comp_incidence', 'nb_comp_incidence', 'deaths_and_stillbirths', 'service_coverage', + 'yearly_mnh_counter_dict']} + +import matplotlib.pyplot as plt +import numpy as np +# Sample data +mmr_data = { + 'int_1': [(235, 250, 265), (335, 350, 365)], + 'int_2': [(170, 195, 200), (290, 305, 320)], + 'int_3': [(280, 295, 310), (295 ,310, 325)], + 'int_4': [(165, 180, 195), (385, 400, 415)] +} +# Plotting +fig, ax = plt.subplots() +for key, intervals in mmr_data.items(): + for idx, (lower, mean, upper) in enumerate(intervals): + x = np.arange(len(mmr_data)) * len(intervals) + idx + ax.plot(x, mean, 'o', label=f'{key}' if idx == 0 else "") + ax.fill_between([x, x], [lower, lower], [upper, upper], alpha=0.2) +ax.set_xticks(np.arange(len(mmr_data)) * len(intervals) + 0.5) +ax.set_xticklabels(mmr_data.keys()) +plt.legend() +plt.show() diff --git a/src/scripts/maternal_perinatal_analyses/cohort_analysis/local_run_cohort_calibration.py b/src/scripts/maternal_perinatal_analyses/cohort_analysis/local_run_cohort_calibration.py new file mode 100644 index 0000000000..a93cdfb61a --- /dev/null +++ b/src/scripts/maternal_perinatal_analyses/cohort_analysis/local_run_cohort_calibration.py @@ -0,0 +1,116 @@ +import os +from pathlib import Path + +import pandas as pd + +from tlo import Date, Simulation, logging +from tlo.methods import mnh_cohort_module +from tlo.methods.fullmodel import fullmodel +from tlo.analysis.utils import parse_log_file + + +resourcefilepath = Path('./resources') +outputpath = Path("./outputs/cohort_testing") # folder for convenience of storing outputs +population_size = 2000 + +sim = Simulation(start_date=Date(2024, 1, 1), + seed=456, + log_config={"filename": "log_cohort_calibration", + "custom_levels": {"*": logging.DEBUG}, + "directory": outputpath}) + +sim.register(*fullmodel(resourcefilepath=resourcefilepath), + mnh_cohort_module.MaternalNewbornHealthCohort(resourcefilepath=resourcefilepath)) + +sim.make_initial_population(n=population_size) +sim.simulate(end_date=Date(2025, 1, 1)) + +output = parse_log_file(sim.log_filepath) + +# output = parse_log_file( +# '/Users/j_collins/PycharmProjects/TLOmodel/outputs/log_cohort_calibration__2024-10-04T101535.log') + +# Make output dataframe +results = pd.DataFrame(columns=['model', 'data', 'source'], + index= ['deaths', + 'MMR', + 'DALYs', + 'twins', + 'ectopic', + 'abortion', + 'miscarriage', + 'syphilis', + 'anaemia_an', + 'anaemia_pn' + 'gdm', + 'PROM', + 'pre_eclampsia', + 'gest-htn', + 'severe_gest-htn', + 'severe pre-eclampsia', + 'eclampsia', + 'praevia', + 'abruption', + 'aph', + 'OL', + 'UR', + 'sepsis', + 'PPH']) + +# total_pregnancies = population_size +total_pregnancies = 2000 +total_births = len(output['tlo.methods.demography']['on_birth']) +prop_live_births = (total_births/total_pregnancies) * 100 + +# Mortality/DALY +deaths_df = output['tlo.methods.demography']['death'] +prop_deaths_df = output['tlo.methods.demography.detail']['properties_of_deceased_persons'] + +dir_mat_deaths = deaths_df.loc[(deaths_df['label'] == 'Maternal Disorders')] +init_indir_mat_deaths = prop_deaths_df.loc[(prop_deaths_df['is_pregnant'] | prop_deaths_df['la_is_postpartum']) & + (prop_deaths_df['cause_of_death'].str.contains('Malaria|Suicide|ever_stroke|diabetes|' + 'chronic_ischemic_hd|ever_heart_attack|' + 'chronic_kidney_disease') | + (prop_deaths_df['cause_of_death'] == 'TB'))] + +hiv_mat_deaths = prop_deaths_df.loc[(prop_deaths_df['is_pregnant'] | prop_deaths_df['la_is_postpartum']) & + (prop_deaths_df['cause_of_death'].str.contains('AIDS_non_TB|AIDS_TB'))] + +indir_mat_deaths = len(init_indir_mat_deaths) + (len(hiv_mat_deaths) * 0.3) +total_deaths = len(dir_mat_deaths) + indir_mat_deaths + +# TOTAL_DEATHS +results.at['deaths', 'model'] = total_deaths +results.at['MMR', 'model'] = (total_deaths / total_births) * 100_000 +results.at['DALYs', 'model'] = output['tlo.methods.healthburden']['dalys_stacked']['Maternal Disorders'].sum() + +# Maternal conditions +an_comps = output['tlo.methods.pregnancy_supervisor']['maternal_complication'] +la_comps = output['tlo.methods.labour']['maternal_complication'] +pn_comps = output['tlo.methods.postnatal_supervisor']['maternal_complication'] + +twin_births = len(output['tlo.methods.newborn_outcomes']['twin_birth']) + +total_completed_pregnancies = (len(an_comps.loc[an_comps['type'] == 'ectopic_unruptured']) + + len(an_comps.loc[an_comps['type'] == 'induced_abortion']) + + len(an_comps.loc[an_comps['type'] == 'spontaneous_abortion']) + + (total_births - twin_births) + + len(output['tlo.methods.pregnancy_supervisor']['antenatal_stillbirth']) + + len(output['tlo.methods.labour']['intrapartum_stillbirth'])) + +print(total_completed_pregnancies) # this value may be less than the starting population size due to antenatal +# maternal deaths + +# Twins (todo) + +# Ectopic +results.at['ectopic', 'model'] = (len(an_comps.loc[an_comps['type'] == 'ectopic_unruptured']) / total_pregnancies) * 1000 +results.at['ectopic', 'data'] = 10.0 +results.at['ectopic', 'source'] = 'Panelli et al.' + +# Abortion + + +# Miscarriage + +# Health system diff --git a/src/scripts/maternal_perinatal_analyses/scenario_files/full_model_long_run_cohort.py b/src/scripts/maternal_perinatal_analyses/scenario_files/full_model_long_run_cohort.py new file mode 100644 index 0000000000..b0a5072bc7 --- /dev/null +++ b/src/scripts/maternal_perinatal_analyses/scenario_files/full_model_long_run_cohort.py @@ -0,0 +1,35 @@ +from tlo import Date, logging +from tlo.methods.fullmodel import fullmodel + +from tlo.scenario import BaseScenario + + +class FullModelRunForCohort(BaseScenario): + def __init__(self): + super().__init__() + self.seed = 537184 + self.start_date = Date(2010, 1, 1) + self.end_date = Date(2025, 1, 1) + self.pop_size = 200_000 + self.number_of_draws = 1 + self.runs_per_draw = 1 + + def log_configuration(self): + return { + 'filename': 'fullmodel_200k_cohort', 'directory': './outputs', + "custom_levels": { + "*": logging.WARNING, + "tlo.methods.contraception": logging.DEBUG, + } + } + + def modules(self): + return fullmodel(resourcefilepath=self.resources) + + def draw_parameters(self, draw_number, rng): + return {} + + +if __name__ == '__main__': + from tlo.cli import scenario_run + scenario_run([__file__]) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 69ce038299..1b458156e4 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -281,7 +281,7 @@ def get_and_store_pregnancy_item_codes(self): # ---------------------------------- IRON AND FOLIC ACID ------------------------------------------------------ # Dose changes at run time self.item_codes_preg_consumables['iron_folic_acid'] = \ - {ic('Ferrous Salt + Folic Acid, tablet, 200 + 0.25 mg'): 1} # TODO: update con requested here + {ic('Ferrous Salt + Folic Acid, tablet, 200 + 0.25 mg'): 1} # --------------------------------- BALANCED ENERGY AND PROTEIN ---------------------------------------------- # Dose changes at run time @@ -484,21 +484,24 @@ def further_on_birth_care_of_women_in_pregnancy(self, mother_id): if df.at[mother_id, 'is_alive']: + anc_count = df.at[mother_id, "ac_total_anc_visits_current_pregnancy"] + # run a check at birth to make sure no women exceed 8 visits - if df.at[mother_id, 'ac_total_anc_visits_current_pregnancy'] > 9: + if anc_count > 9: logger.info(key='error', data=f'Mother {mother_id} attended >8 ANC visits during her pregnancy') - # We log the total number of ANC contacts a woman has undergone at the time of birth via this dictionary + if anc_count > 8: + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['anc8+'] += 1 + else: + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[f'anc{anc_count}'] += 1 + + # We log the gestational age at first ANC if 'ga_anc_one' in mni[mother_id]: ga_anc_one = float(mni[mother_id]['ga_anc_one']) else: ga_anc_one = 0.0 - total_anc_visit_count = {'person_id': mother_id, - 'total_anc': df.at[mother_id, 'ac_total_anc_visits_current_pregnancy'], - 'ga_anc_one': ga_anc_one} - - logger.info(key='anc_count_on_birth', data=total_anc_visit_count, + logger.info(key='ga_at_anc1', data={'person_id': mother_id, 'ga_anc_one': ga_anc_one}, description='A dictionary containing the number of ANC visits each woman has on birth') def on_hsi_alert(self, person_id, treatment_id): @@ -725,45 +728,63 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): params = self.current_parameters mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info - hypertension_diagnosed = False - proteinuria_diagnosed = False - - # Delivery of the intervention is conditioned on a random draw against a probability that the intervention - # would be delivered (used to calibrate to SPA data - acts as proxy for clinical quality) - if self.rng.random_sample() < params['prob_intervention_delivered_urine_ds']: - - # check consumables - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, cons=self.item_codes_preg_consumables['urine_dipstick'], opt_cons=None) - - # If the intervention will be delivered the dx_manager runs, returning True if the consumables are - # available and the test detects protein in the urine - if avail and self.sim.modules['HealthSystem'].dx_manager.run_dx_test( - dx_tests_to_run='urine_dipstick_protein', hsi_event=hsi_event): - - # We use a temporary variable to store if proteinuria is detected - proteinuria_diagnosed = True - logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'dipstick'}) - - # The process is repeated for blood pressure monitoring - if self.rng.random_sample() < params['prob_intervention_delivered_bp']: - hsi_event.add_equipment({'Sphygmomanometer'}) - - if self.sim.modules['HealthSystem'].dx_manager.run_dx_test(dx_tests_to_run='blood_pressure_measurement', - hsi_event=hsi_event): - hypertension_diagnosed = True - logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'bp_measurement'}) - - if not df.at[person_id, 'ac_gest_htn_on_treatment'] and\ - (df.at[person_id, 'ps_htn_disorders'] != 'none') and pd.isnull(mni[person_id]['hypertension' - '_onset']): - - # We store date of onset to calculate dalys- only women who are aware of diagnosis experience DALYs - # (see daly weight for hypertension) - pregnancy_helper_functions.store_dalys_in_mni(person_id, mni, 'hypertension_onset', self.sim.date) - - # If either high blood pressure or proteinuria are detected (or both) we assume this woman needs to be admitted - # for further treatment following this ANC contact + proteinuria_diagnosed = pregnancy_helper_functions.check_int_deliverable( + self, int_name='urine_dipstick', + hsi_event=hsi_event, + q_param=[params['prob_intervention_delivered_urine_ds']], + cons=self.item_codes_preg_consumables['urine_dipstick'], + dx_test='urine_dipstick_protein') + + hypertension_diagnosed = pregnancy_helper_functions.check_int_deliverable( + self, int_name='bp_measurement', + hsi_event=hsi_event, + q_param=[params['prob_intervention_delivered_bp']], + cons=None, + equipment={'Sphygmomanometer'}, + dx_test='blood_pressure_measurement') + + if hypertension_diagnosed and not df.at[person_id, 'ac_gest_htn_on_treatment'] and \ + (df.at[person_id, 'ps_htn_disorders'] != 'none') and pd.isnull(mni[person_id]['hypertension_onset']): + # We store date of onset to calculate dalys- only women who are aware of diagnosis experience DALYs + # (see daly weight for hypertension) + pregnancy_helper_functions.store_dalys_in_mni(person_id, mni, 'hypertension_onset', self.sim.date) + + # # Delivery of the intervention is conditioned on a random draw against a probability that the intervention + # # would be delivered (used to calibrate to SPA data - acts as proxy for clinical quality) + # if self.rng.random_sample() < params['prob_intervention_delivered_urine_ds']: + # + # # check consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, cons=self.item_codes_preg_consumables['urine_dipstick'], opt_cons=None) + # + # # If the intervention will be delivered the dx_manager runs, returning True if the consumables are + # # available and the test detects protein in the urine + # if avail and self.sim.modules['HealthSystem'].dx_manager.run_dx_test( + # dx_tests_to_run='urine_dipstick_protein', hsi_event=hsi_event): + # + # # We use a temporary variable to store if proteinuria is detected + # proteinuria_diagnosed = True + # logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'dipstick'}) + # + # # The process is repeated for blood pressure monitoring + # if self.rng.random_sample() < params['prob_intervention_delivered_bp']: + # hsi_event.add_equipment({'Sphygmomanometer'}) + # + # if self.sim.modules['HealthSystem'].dx_manager.run_dx_test(dx_tests_to_run='blood_pressure_measurement', + # hsi_event=hsi_event): + # hypertension_diagnosed = True + # logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'bp_measurement'}) + # + # if not df.at[person_id, 'ac_gest_htn_on_treatment'] and\ + # (df.at[person_id, 'ps_htn_disorders'] != 'none') and pd.isnull(mni[person_id]['hypertension' + # '_onset']): + # + # # We store date of onset to calculate dalys- only women who are aware of diagnosis experience DALYs + # # (see daly weight for hypertension) + # pregnancy_helper_functions.store_dalys_in_mni(person_id, mni, 'hypertension_onset', self.sim.date) + # + # # If either high blood pressure or proteinuria are detected (or both) we assume this woman needs to be admitted + # # for further treatment following this ANC contact # Only women who are not on treatment OR are determined to have severe disease whilst on treatment are admitted if hypertension_diagnosed or proteinuria_diagnosed: @@ -795,10 +816,18 @@ def iron_and_folic_acid_supplementation(self, hsi_event): days = self.get_approx_days_of_pregnancy(person_id) updated_cons = {k: v*(days*2) for (k, v) in self.item_codes_preg_consumables['iron_folic_acid'].items()} - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, cons=updated_cons, opt_cons=None) + iron_folic_acid_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='iron_folic_acid', + hsi_event=hsi_event, + q_param=None, + cons=updated_cons) + + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, cons=updated_cons, opt_cons=None) + # + # if avail: - if avail: + if iron_folic_acid_delivered: logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'iron_folic_acid'}) # Importantly, only women who will be adherent to iron will experience the benefits of the @@ -832,11 +861,16 @@ def balance_energy_and_protein_supplementation(self, hsi_event): updated_cons = {k: v*days for (k, v) in self.item_codes_preg_consumables['balanced_energy_protein'].items()} - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, cons=updated_cons, opt_cons=None) + bep_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='protein_supplement', hsi_event=hsi_event, q_param=None, cons=updated_cons) + + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, cons=updated_cons, opt_cons=None) + # + # # And she is deemed to be at risk (i.e. BMI < 18) she is started on supplements + # if avail and (df.at[person_id, 'li_bmi'] == 1): - # And she is deemed to be at risk (i.e. BMI < 18) she is started on supplements - if avail and (df.at[person_id, 'li_bmi'] == 1): + if bep_delivered and (df.at[person_id, 'li_bmi'] == 1): df.at[person_id, 'ac_receiving_bep_supplements'] = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'b_e_p'}) @@ -898,10 +932,15 @@ def calcium_supplementation(self, hsi_event): updated_cons = {k: v * days for (k, v) in self.item_codes_preg_consumables['calcium'].items()} - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, cons=updated_cons, opt_cons=None) + calcium_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='calcium_supplement', hsi_event=hsi_event, q_param=None, cons=updated_cons) - if avail: + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, cons=updated_cons, opt_cons=None) + # + # if avail: + + if calcium_delivered: df.at[person_id, 'ac_receiving_calcium_supplements'] = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'calcium'}) @@ -920,16 +959,23 @@ def point_of_care_hb_testing(self, hsi_event): logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'hb_screen'}) - # Run check against probability of testing being delivered - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + hb_test_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='hb_test', hsi_event=hsi_event, q_param=None, cons=self.item_codes_preg_consumables['hb_test'], - opt_cons=self.item_codes_preg_consumables['blood_test_equipment']) - - # We run the test through the dx_manager and if a woman has anaemia and its detected she will be admitted - # for further care - if avail and self.sim.modules['HealthSystem'].dx_manager.run_dx_test(dx_tests_to_run='point_of_care_hb_test', - hsi_event=hsi_event): + opt_cons=self.item_codes_preg_consumables['blood_test_equipment'], + dx_test='point_of_care_hb_test') + + # # Run check against probability of testing being delivered + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_preg_consumables['hb_test'], + # opt_cons=self.item_codes_preg_consumables['blood_test_equipment']) + + # # We run the test through the dx_manager and if a woman has anaemia and its detected she will be admitted + # # for further care + # if avail and self.sim.modules['HealthSystem'].dx_manager.run_dx_test(dx_tests_to_run='point_of_care_hb_test', + # hsi_event=hsi_event): + if hb_test_delivered: df.at[person_id, 'ac_to_be_admitted'] = True def albendazole_administration(self, hsi_event): @@ -993,30 +1039,47 @@ def syphilis_screening_and_treatment(self, hsi_event): if not self.check_intervention_should_run_and_update_mni(person_id, 'syph_1', 'syph_2'): return - # See if she will receive testing - if self.rng.random_sample() < params['prob_intervention_delivered_syph_test']: - logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'syphilis_test'}) - - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, - cons=self.item_codes_preg_consumables['syphilis_test'], - opt_cons=self.item_codes_preg_consumables['blood_test_equipment']) - - test = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( - dx_tests_to_run='blood_test_syphilis', hsi_event=hsi_event) - - # If the testing occurs and detects syphilis she will get treatment (if consumables are available) - if avail and test: - - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, - cons=self.item_codes_preg_consumables['syphilis_treatment'], - opt_cons=self.item_codes_preg_consumables['blood_test_equipment']) + syph_test_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='syphilis_test', hsi_event=hsi_event, + q_param=[params['prob_intervention_delivered_syph_test']], + cons=self.item_codes_preg_consumables['syphilis_test'], + opt_cons=self.item_codes_preg_consumables['blood_test_equipment'], + dx_test='blood_test_syphilis') + + + # # See if she will receive testing + # if self.rng.random_sample() < params['prob_intervention_delivered_syph_test']: + # logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'syphilis_test'}) + # + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_preg_consumables['syphilis_test'], + # opt_cons=self.item_codes_preg_consumables['blood_test_equipment']) + # + # test = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( + # dx_tests_to_run='blood_test_syphilis', hsi_event=hsi_event) + + # # If the testing occurs and detects syphilis she will get treatment (if consumables are available) + # if avail and test: + + if syph_test_delivered: + + syph_treatment_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='syphilis_treatment', hsi_event=hsi_event, + q_param=None, + cons=self.item_codes_preg_consumables['syphilis_treatment'], + opt_cons=self.item_codes_preg_consumables['blood_test_equipment']) - if avail: - # We assume that treatment is 100% effective at curing infection - df.at[person_id, 'ps_syphilis'] = False - logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'syphilis_treat'}) + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_preg_consumables['syphilis_treatment'], + # opt_cons=self.item_codes_preg_consumables['blood_test_equipment']) + # + # # if avail: + if syph_treatment_delivered: + # We assume that treatment is 100% effective at curing infection + df.at[person_id, 'ps_syphilis'] = False + logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'syphilis_treat'}) def hiv_testing(self, hsi_event): """ @@ -1072,34 +1135,45 @@ def gdm_screening(self, hsi_event): if df.at[person_id, 'li_bmi'] >= 4 or df.at[person_id, 'ps_prev_gest_diab'] or df.at[person_id, 'ps_prev_stillbirth']: - # If they are available, the test is conducted - if self.rng.random_sample() < params['prob_intervention_delivered_gdm_test']: - - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, - cons=self.item_codes_preg_consumables['gdm_test'], - opt_cons=self.item_codes_preg_consumables['blood_test_equipment']) - - # If the test accurately detects a woman has gestational diabetes the consumables are recorded and - # she is referred for treatment - if avail: - hsi_event.add_equipment({'Glucometer'}) - - if ( - self.sim.modules['HealthSystem'].dx_manager.run_dx_test( - dx_tests_to_run='blood_test_glucose', hsi_event=hsi_event) - ): - logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'gdm_screen'}) - mni[person_id]['anc_ints'].append('gdm_screen') + gdm_screening_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='gdm_test', hsi_event=hsi_event, + q_param=[params['prob_intervention_delivered_gdm_test']], + cons=self.item_codes_preg_consumables['gdm_test'], + opt_cons=self.item_codes_preg_consumables['blood_test_equipment'], + dx_test='blood_test_glucose', + equipment={'Glucometer'}) + + + # # If they are available, the test is conducted + # if self.rng.random_sample() < params['prob_intervention_delivered_gdm_test']: + # + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_preg_consumables['gdm_test'], + # opt_cons=self.item_codes_preg_consumables['blood_test_equipment']) + # + # # If the test accurately detects a woman has gestational diabetes the consumables are recorded and + # # she is referred for treatment + # if avail: + # hsi_event.add_equipment({'Glucometer'}) + # + # if ( + # self.sim.modules['HealthSystem'].dx_manager.run_dx_test( + # dx_tests_to_run='blood_test_glucose', hsi_event=hsi_event) + # ): + # logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'gdm_screen'}) + # mni[person_id]['anc_ints'].append('gdm_screen') # We assume women with a positive GDM screen will be admitted (if they are not already receiving # outpatient care) - if df.at[person_id, 'ac_gest_diab_on_treatment'] == 'none': - # Store onset after diagnosis as daly weight is tied to diagnosis - pregnancy_helper_functions.store_dalys_in_mni(person_id, mni, 'gest_diab_onset', - self.sim.date) - df.at[person_id, 'ac_to_be_admitted'] = True + if gdm_screening_delivered: + if df.at[person_id, 'ac_gest_diab_on_treatment'] == 'none': + + # Store onset after diagnosis as daly weight is tied to diagnosis + pregnancy_helper_functions.store_dalys_in_mni(person_id, mni, 'gest_diab_onset', + self.sim.date) + df.at[person_id, 'ac_to_be_admitted'] = True def interventions_delivered_each_visit_from_anc2(self, hsi_event): """This function contains a collection of interventions that are delivered to women every time they attend ANC @@ -1214,20 +1288,26 @@ def full_blood_count_testing(self, hsi_event): df = self.sim.population.props person_id = hsi_event.target - # Run dx_test for anaemia... - # If a woman is not truly anaemic but the FBC returns a result of anaemia, due to tests specificity, we - # assume the reported anaemia is mild - hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_equipment']) - hsi_event.add_equipment({'Analyser, Haematology'}) - - test_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( - dx_tests_to_run='full_blood_count_hb', hsi_event=hsi_event) - - if test_result and (df.at[person_id, 'ps_anaemia_in_pregnancy'] == 'none'): + # # Run dx_test for anaemia... + # # If a woman is not truly anaemic but the FBC returns a result of anaemia, due to tests specificity, we + # # assume the reported anaemia is mild + # hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_equipment']) + # hsi_event.add_equipment({'Analyser, Haematology'}) + # + # test_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( + # dx_tests_to_run='full_blood_count_hb', hsi_event=hsi_event) + + full_blood_count_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='full_blood_count', hsi_event=hsi_event, + q_param=None,cons=None, + opt_cons=self.item_codes_preg_consumables['blood_test_equipment'], + equipment={'Analyser, Haematology'}, dx_test='full_blood_count_hb') + + if full_blood_count_delivered and (df.at[person_id, 'ps_anaemia_in_pregnancy'] == 'none'): return 'non_severe' # If the test correctly identifies a woman's anaemia we assume it correctly identifies its severity - if test_result and (df.at[person_id, 'ps_anaemia_in_pregnancy'] != 'none'): + if full_blood_count_delivered and (df.at[person_id, 'ps_anaemia_in_pregnancy'] != 'none'): return df.at[person_id, 'ps_anaemia_in_pregnancy'] # We return a none value if no anaemia was detected @@ -1244,24 +1324,34 @@ def antenatal_blood_transfusion(self, individual_id, hsi_event): """ df = self.sim.population.props params = self.current_parameters + l_params = self.sim.modules['Labour'].current_parameters store_dalys_in_mni = pregnancy_helper_functions.store_dalys_in_mni mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info - # Check for consumables - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + # # Check for consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_preg_consumables['blood_transfusion'], + # opt_cons=self.item_codes_preg_consumables['iv_drug_equipment']) + # + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], + # sf='blood_tran', + # hsi_event=hsi_event) + # + # # If the blood is available we assume the intervention can be delivered + # if avail and sf_check: + + blood_transfusion_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='blood_transfusion', hsi_event=hsi_event, + q_param=[l_params['prob_hcw_avail_blood_tran'], l_params['mean_hcw_competence_hp']], cons=self.item_codes_preg_consumables['blood_transfusion'], - opt_cons=self.item_codes_preg_consumables['iv_drug_equipment']) - - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], - sf='blood_tran', - hsi_event=hsi_event) + opt_cons=self.item_codes_preg_consumables['blood_test_equipment'], + equipment={'Drip stand', 'Infusion pump'}) - # If the blood is available we assume the intervention can be delivered - if avail and sf_check: + if blood_transfusion_delivered: pregnancy_helper_functions.log_met_need(self, 'blood_tran', hsi_event) - hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) + # hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) # If the woman is receiving blood due to anaemia we apply a probability that a transfusion of 2 units # RBCs will correct this woman's severe anaemia @@ -1284,11 +1374,16 @@ def initiate_maintenance_anti_hypertensive_treatment(self, individual_id, hsi_ev updated_cons = {k: v * days for (k, v) in self.item_codes_preg_consumables['oral_antihypertensives'].items()} - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, cons=updated_cons, opt_cons=None) + oral_anti_htns_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='oral_antihypertensives', hsi_event=hsi_event, + q_param=None, cons=updated_cons) + # + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, cons=updated_cons, opt_cons=None) + # # If the consumables are available then the woman is started on treatment + # if avail: - # If the consumables are available then the woman is started on treatment - if avail: + if oral_anti_htns_delivered: df.at[individual_id, 'ac_gest_htn_on_treatment'] = True def initiate_treatment_for_severe_hypertension(self, individual_id, hsi_event): @@ -1302,16 +1397,23 @@ def initiate_treatment_for_severe_hypertension(self, individual_id, hsi_event): """ df = self.sim.population.props - # Define the consumables and check their availability - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, - cons=self.item_codes_preg_consumables['iv_antihypertensives'], - opt_cons=self.item_codes_preg_consumables['iv_drug_equipment']) - - # If they are available then the woman is started on treatment - if avail: + # # Define the consumables and check their availability + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_preg_consumables['iv_antihypertensives'], + # opt_cons=self.item_codes_preg_consumables['iv_drug_equipment']) + # + # # If they are available then the woman is started on treatment + # if avail: + + iv_anti_htns_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='oral_antihypertensives', hsi_event=hsi_event, + q_param=None, cons=self.item_codes_preg_consumables['iv_antihypertensives'], + opt_cons=self.item_codes_preg_consumables['iv_drug_equipment'], equipment={'Drip stand', 'Infusion pump'}) + + if iv_anti_htns_delivered: pregnancy_helper_functions.log_met_need(self, 'iv_htns', hsi_event) - hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) + # hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) # We assume women treated with antihypertensives would no longer be severely hypertensive- meaning they # are not at risk of death from severe gestational hypertension in the PregnancySupervisor event @@ -1335,22 +1437,32 @@ def treatment_for_severe_pre_eclampsia_or_eclampsia(self, individual_id, hsi_eve :param hsi_event: HSI event in which the function has been called """ df = self.sim.population.props - - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + l_params = self.sim.modules['Labour'].current_parameters + + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_preg_consumables['magnesium_sulfate'], + # opt_cons=self.item_codes_preg_consumables['eclampsia_management_optional']) + # + # # check HCW will deliver intervention + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], + # sf='anticonvulsant', + # hsi_event=hsi_event) + # + # # If available deliver the treatment + # if avail and sf_check: + + mag_sulph_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='mgso4', hsi_event=hsi_event, + q_param=[l_params['prob_hcw_avail_anticonvulsant'], l_params['mean_hcw_competence_hp']], cons=self.item_codes_preg_consumables['magnesium_sulfate'], - opt_cons=self.item_codes_preg_consumables['eclampsia_management_optional']) - - # check HCW will deliver intervention - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], - sf='anticonvulsant', - hsi_event=hsi_event) + opt_cons=self.item_codes_preg_consumables['eclampsia_management_optional'], + equipment={'Drip stand', 'Infusion pump'}) - # If available deliver the treatment - if avail and sf_check: + if mag_sulph_delivered: df.at[individual_id, 'ac_mag_sulph_treatment'] = True pregnancy_helper_functions.log_met_need(self, 'mag_sulph', hsi_event) - hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) + # hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) def antibiotics_for_prom(self, individual_id, hsi_event): """ @@ -1360,20 +1472,29 @@ def antibiotics_for_prom(self, individual_id, hsi_event): :param hsi_event: HSI event in which the function has been called """ df = self.sim.population.props - - # check consumables and whether HCW are available to deliver the intervention - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + l_params = self.sim.modules['Labour'].current_parameters + + # # check consumables and whether HCW are available to deliver the intervention + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_preg_consumables['abx_for_prom'], + # opt_cons=self.item_codes_preg_consumables['iv_drug_equipment']) + # + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], + # sf='iv_abx', + # hsi_event=hsi_event) + # + # if avail and sf_check: + abx_prom_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='abx_for_prom', hsi_event=hsi_event, + q_param=[l_params['prob_hcw_avail_iv_abx'], l_params['mean_hcw_competence_hp']], cons=self.item_codes_preg_consumables['abx_for_prom'], - opt_cons=self.item_codes_preg_consumables['iv_drug_equipment']) + opt_cons=self.item_codes_preg_consumables['iv_drug_equipment'], + equipment={'Drip stand', 'Infusion pump'}) - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], - sf='iv_abx', - hsi_event=hsi_event) - - if avail and sf_check: + if abx_prom_delivered: df.at[individual_id, 'ac_received_abx_for_prom'] = True - hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) + # hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) def ectopic_pregnancy_treatment_doesnt_run(self, hsi_event): """ @@ -2519,11 +2640,17 @@ def schedule_gdm_event_and_checkup(): updated_cons = {k: v * days for (k, v) in self.module.item_codes_preg_consumables['oral_diabetic_treatment'].items()} - avail = pregnancy_helper_functions.return_cons_avail( - self.module, self, cons=updated_cons, opt_cons=None) + # avail = pregnancy_helper_functions.return_cons_avail( + # self.module, self, cons=updated_cons, opt_cons=None) + # + # # If the meds are available women are started on that treatment + # if avail: + + gdm_orals_delivered = pregnancy_helper_functions.check_int_deliverable( + self.module, int_name='gdm_treatment_orals', hsi_event=self, + q_param=None, cons=updated_cons) - # If the meds are available women are started on that treatment - if avail: + if gdm_orals_delivered: df.at[person_id, 'ac_gest_diab_on_treatment'] = 'orals' # Assume new treatment is effective in controlling blood glucose on initiation @@ -2543,10 +2670,16 @@ def schedule_gdm_event_and_checkup(): updated_cons = {k: v * required_vials for (k, v) in self.module.item_codes_preg_consumables['insulin_treatment'].items()} - avail = pregnancy_helper_functions.return_cons_avail( - self.module, self, cons=updated_cons, opt_cons=None) + # avail = pregnancy_helper_functions.return_cons_avail( + # self.module, self, cons=updated_cons, opt_cons=None) + # + # if avail: + + gdm_insulin_delivered = pregnancy_helper_functions.check_int_deliverable( + self.module, int_name='gdm_treatment_insulin', hsi_event=self, + q_param=None, cons=updated_cons) - if avail: + if gdm_insulin_delivered: df.at[person_id, 'ac_gest_diab_on_treatment'] = 'insulin' df.at[person_id, 'ps_gest_diab'] = 'controlled' @@ -2581,65 +2714,91 @@ def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] abortion_complications = self.sim.modules['PregnancySupervisor'].abortion_complications + l_params = self.sim.modules['Labour'].current_parameters if not mother.is_alive or not abortion_complications.has_any([person_id], 'sepsis', 'haemorrhage', 'injury', 'other', first=True): return - # Request baseline PAC consumables - baseline_cons = pregnancy_helper_functions.return_cons_avail( - self.module, self, - cons=self.module.item_codes_preg_consumables['post_abortion_care_core'], - opt_cons=self.module.item_codes_preg_consumables['post_abortion_care_optional']) + pac_cons = self.module.item_codes_preg_consumables['post_abortion_care_core'] + pac_opt_cons = self.module.item_codes_preg_consumables['post_abortion_care_optional'] - # Check HCW availability to deliver surgical removal of retained products - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], - sf='retained_prod', - hsi_event=self) - - # Add used equipment if intervention can happen - if baseline_cons and sf_check: - self.add_equipment({'D&C set', 'Suction Curettage machine', 'Drip stand', 'Infusion pump'}) - - # Then we determine if a woman gets treatment for her complication depending on availability of the baseline - # consumables (misoprostol) or a HCW who can conduct MVA/DC (we dont model equipment) and additional - # consumables for management of her specific complication if abortion_complications.has_any([person_id], 'sepsis', first=True): + pac_cons.update(self.module.item_codes_preg_consumables['post_abortion_care_sepsis_core']) + pac_opt_cons.update(self.module.item_codes_preg_consumables['post_abortion_care_sepsis_optional']) - cons_for_sepsis_pac = pregnancy_helper_functions.return_cons_avail( - self.module, self, - cons=self.module.item_codes_preg_consumables['post_abortion_care_sepsis_core'], - opt_cons=self.module.item_codes_preg_consumables['post_abortion_care_sepsis_optional']) - - if cons_for_sepsis_pac and (baseline_cons or sf_check): - df.at[person_id, 'ac_received_post_abortion_care'] = True + if abortion_complications.has_any([person_id], 'haemorrhage', first=True): + pac_cons.update(self.module.item_codes_preg_consumables['blood_transfusion']) + pac_opt_cons.update(self.module.item_codes_preg_consumables['iv_drug_equipment']) - elif abortion_complications.has_any([person_id], 'haemorrhage', first=True): - cons_for_haemorrhage = pregnancy_helper_functions.return_cons_avail( - self.module, self, - cons=self.module.item_codes_preg_consumables['blood_transfusion'], - opt_cons=self.module.item_codes_preg_consumables['iv_drug_equipment']) + if abortion_complications.has_any([person_id], 'injury', first=True): + pac_cons.update(self.module.item_codes_preg_consumables['post_abortion_care_shock']) + pac_opt_cons.update(self.module.item_codes_preg_consumables['post_abortion_care_shock_optional']) - cons_for_shock = pregnancy_helper_functions.return_cons_avail( - self.module, self, - cons=self.module.item_codes_preg_consumables['post_abortion_care_shock'], - opt_cons=self.module.item_codes_preg_consumables['post_abortion_care_shock_optional']) + pac_delivered = pregnancy_helper_functions.check_int_deliverable( + self.module, int_name='post_abortion_care_core', hsi_event=self, + q_param=[l_params['prob_hcw_avail_retained_prod'], l_params['mean_hcw_competence_hp']], + cons=pac_cons, + opt_cons=pac_opt_cons, + equipment={'D&C set', 'Suction Curettage machine', 'Drip stand', 'Infusion pump'}) - if cons_for_haemorrhage and cons_for_shock and (baseline_cons or sf_check): - df.at[person_id, 'ac_received_post_abortion_care'] = True - - elif abortion_complications.has_any([person_id], 'injury', first=True): - cons_for_shock = pregnancy_helper_functions.return_cons_avail( - self.module, self, - cons=self.module.item_codes_preg_consumables['post_abortion_care_shock'], - opt_cons=self.module.item_codes_preg_consumables['post_abortion_care_shock_optional']) - - if cons_for_shock and (baseline_cons or sf_check): - df.at[person_id, 'ac_received_post_abortion_care'] = True - - elif abortion_complications.has_any([person_id], 'other', first=True) and (baseline_cons or sf_check): + if pac_delivered: df.at[person_id, 'ac_received_post_abortion_care'] = True + # # Request baseline PAC consumables + # baseline_cons = pregnancy_helper_functions.return_cons_avail( + # self.module, self, + # cons=self.module.item_codes_preg_consumables['post_abortion_care_core'], + # opt_cons=self.module.item_codes_preg_consumables['post_abortion_care_optional']) + # + # # Check HCW availability to deliver surgical removal of retained products + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], + # sf='retained_prod', + # hsi_event=self) + # + # # Add used equipment if intervention can happen + # if baseline_cons and sf_check: + # self.add_equipment({'D&C set', 'Suction Curettage machine', 'Drip stand', 'Infusion pump'}) + # + # # Then we determine if a woman gets treatment for her complication depending on availability of the baseline + # # consumables (misoprostol) or a HCW who can conduct MVA/DC (we dont model equipment) and additional + # # consumables for management of her specific complication + # if abortion_complications.has_any([person_id], 'sepsis', first=True): + # + # cons_for_sepsis_pac = pregnancy_helper_functions.return_cons_avail( + # self.module, self, + # cons=self.module.item_codes_preg_consumables['post_abortion_care_sepsis_core'], + # opt_cons=self.module.item_codes_preg_consumables['post_abortion_care_sepsis_optional']) + # + # if cons_for_sepsis_pac and (baseline_cons or sf_check): + # df.at[person_id, 'ac_received_post_abortion_care'] = True + # + # elif abortion_complications.has_any([person_id], 'haemorrhage', first=True): + # cons_for_haemorrhage = pregnancy_helper_functions.return_cons_avail( + # self.module, self, + # cons=self.module.item_codes_preg_consumables['blood_transfusion'], + # opt_cons=self.module.item_codes_preg_consumables['iv_drug_equipment']) + # + # cons_for_shock = pregnancy_helper_functions.return_cons_avail( + # self.module, self, + # cons=self.module.item_codes_preg_consumables['post_abortion_care_shock'], + # opt_cons=self.module.item_codes_preg_consumables['post_abortion_care_shock_optional']) + # + # if cons_for_haemorrhage and cons_for_shock and (baseline_cons or sf_check): + # df.at[person_id, 'ac_received_post_abortion_care'] = True + # + # elif abortion_complications.has_any([person_id], 'injury', first=True): + # cons_for_shock = pregnancy_helper_functions.return_cons_avail( + # self.module, self, + # cons=self.module.item_codes_preg_consumables['post_abortion_care_shock'], + # opt_cons=self.module.item_codes_preg_consumables['post_abortion_care_shock_optional']) + # + # if cons_for_shock and (baseline_cons or sf_check): + # df.at[person_id, 'ac_received_post_abortion_care'] = True + # + # elif abortion_complications.has_any([person_id], 'other', first=True) and (baseline_cons or sf_check): + # df.at[person_id, 'ac_received_post_abortion_care'] = True + if df.at[person_id, 'ac_received_post_abortion_care']: pregnancy_helper_functions.log_met_need(self.module, 'pac', self) @@ -2671,21 +2830,31 @@ def __init__(self, module, person_id): def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] + l_params = self.sim.modules['Labour'].current_parameters if not mother.is_alive or (mother.ps_ectopic_pregnancy == 'none'): return - # We define the required consumables and check their availability - avail = pregnancy_helper_functions.return_cons_avail( - self.module, self, + ep_surg_delivered = pregnancy_helper_functions.check_int_deliverable( + self.module, int_name='ectopic_pregnancy_treatment', hsi_event=self, + q_param=[l_params['prob_hcw_avail_surg'], l_params['mean_hcw_competence_hp']], cons=self.module.item_codes_preg_consumables['ectopic_pregnancy_core'], - opt_cons=self.module.item_codes_preg_consumables['ectopic_pregnancy_optional']) - - # If they are available then treatment can go ahead - if avail: + opt_cons=self.module.item_codes_preg_consumables['ectopic_pregnancy_optional'], + equipment={'Laparotomy Set'}) + + # # We define the required consumables and check their availability + # avail = pregnancy_helper_functions.return_cons_avail( + # self.module, self, + # cons=self.module.item_codes_preg_consumables['ectopic_pregnancy_core'], + # opt_cons=self.module.item_codes_preg_consumables['ectopic_pregnancy_optional']) + # + # # If they are available then treatment can go ahead + # if avail: + + if ep_surg_delivered: self.sim.modules['PregnancySupervisor'].mother_and_newborn_info[person_id]['delete_mni'] = True pregnancy_helper_functions.log_met_need(self.module, 'ep_case_mang', self) - self.add_equipment({'Laparotomy Set'}) + # self.add_equipment({'Laparotomy Set'}) # For women who have sought care after they have experienced rupture we use this treatment variable to # reduce risk of death (women who present prior to rupture do not pass through the death event as we assume diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index ab6c633f4c..3a6085226f 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1105,6 +1105,12 @@ def set_new_pregnancy(self, women_id: list): }, description='pregnancy following the failure of contraceptive method') + person = df.loc[w] + + logger.debug(key='properties_of_pregnant_person', + data=person.to_dict(), + description='values of all properties at the time of pregnancy for newwly pregnany persons') + class ContraceptionLoggingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): diff --git a/src/tlo/methods/demography.py b/src/tlo/methods/demography.py index e58f3895f4..dbc7656b90 100644 --- a/src/tlo/methods/demography.py +++ b/src/tlo/methods/demography.py @@ -792,8 +792,8 @@ def apply(self, population): logger.info( key='population', data={'total': sum(sex_count), - 'male': sex_count['M'], - 'female': sex_count['F'] + 'male': sex_count['M'] if 'M' in sex_count.index else 0, + 'female': sex_count['F'] if 'F' in sex_count.index else 0, }) # (nb. if you groupby both sex and age_range, you weirdly lose categories where size==0, so diff --git a/src/tlo/methods/epi.py b/src/tlo/methods/epi.py index 1cdf8a1612..f39bb44c63 100644 --- a/src/tlo/methods/epi.py +++ b/src/tlo/methods/epi.py @@ -181,8 +181,12 @@ def initialise_simulation(self, sim): # add an event to log to screen sim.schedule_event(EpiLoggingEvent(self), sim.date + DateOffset(years=1)) + # TODO: check with Tara shes happy with this (could come in as its own PR) # HPV vaccine given from 2018 onwards - sim.schedule_event(HpvScheduleEvent(self), Date(2018, 1, 1)) + if self.sim.date.year < 2018: + sim.schedule_event(HpvScheduleEvent(self), Date(2018, 1, 1)) + else: + sim.schedule_event(HpvScheduleEvent(self), Date(self.sim.date.year, 1, 1)) # Look up item codes for consumables self.get_item_codes() diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index d6455cc861..4c956de29c 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -1504,7 +1504,7 @@ def per_capita_testing_rate(self): df = self.sim.population.props # get number of tests performed in last time period - if self.sim.date.year == 2011: + if self.sim.date.year == (self.sim.start_date.year + 1): number_tests_new = df.hv_number_tests.sum() previous_test_numbers = 0 diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 35081b7d27..f9662ed7d3 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -153,8 +153,6 @@ def __init__(self, name=None, resourcefilepath=None): 'list_limits_for_defining_term_status': Parameter( Types.LIST, 'List of number of days of gestation used to define term, early preterm, late preterm and ' 'post term delivery'), - 'allowed_interventions': Parameter( - Types.LIST, 'list of interventions allowed to run, used in analysis'), # BIRTH WEIGHT... 'mean_birth_weights': Parameter( @@ -868,18 +866,19 @@ def get_and_store_labour_item_codes(self): def initialise_simulation(self, sim): # Update self.current_parameters - pregnancy_helper_functions.update_current_parameter_dictionary(self, list_position=0) + # pregnancy_helper_functions.update_current_parameter_dictionary(self, list_position=0) # We call the following function to store the required consumables for the simulation run within the appropriate # dictionary self.get_and_store_labour_item_codes() # We set the LoggingEvent to run on the last day of each year to produce statistics for that year - sim.schedule_event(LabourLoggingEvent(self), sim.date + DateOffset(days=1)) + sim.schedule_event(LabourLoggingEvent(self), sim.date + DateOffset(years=1)) # Schedule analysis event - sim.schedule_event(LabourAndPostnatalCareAnalysisEvent(self), - Date(self.current_parameters['analysis_year'], 1, 1)) + if self.sim.date.year <= self.current_parameters['analysis_year']: + sim.schedule_event(LabourAndPostnatalCareAnalysisEvent(self), + Date(self.current_parameters['analysis_year'], 1, 1)) # This list contains all the women who are currently in labour and is used for checks/testing self.women_in_labour = [] @@ -900,7 +899,6 @@ def initialise_simulation(self, sim): full_blood_count_hb_pn=DxTest( property='pn_anaemia_following_pregnancy', target_categories=['mild', 'moderate', 'severe'], - item_codes=self.item_codes_lab_consumables['full_blood_count'], sensitivity=1.0), ) @@ -1056,9 +1054,10 @@ def further_on_birth_labour(self, mother_id): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info # log delivery setting - logger.info(key='delivery_setting_and_mode', data={'mother': mother_id, - 'facility_type': str(mni[mother_id]['delivery_setting']), - 'mode': mni[mother_id]['mode_of_delivery']}) + logger.info(key='delivery_mode', data={'mother': mother_id, 'mode': mni[mother_id]['mode_of_delivery']}) + + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[ + f'{str(mni[mother_id]["delivery_setting"])}_delivery'] += 1 # Store only live births to a mother parity if not df.at[mother_id, 'la_intrapartum_still_birth']: @@ -1282,9 +1281,7 @@ def set_intrapartum_complications(self, individual_id, complication): pregnancy_helper_functions.store_dalys_in_mni(individual_id, mni, 'obstructed_labour_onset', self.sim.date) - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': f'{complication}', - 'timing': 'intrapartum'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[complication] += 1 if complication == 'obstruction_cpd': mni[individual_id]['cpd'] = True @@ -1293,9 +1290,8 @@ def set_intrapartum_complications(self, individual_id, complication): # to labour) elif complication == 'placental_abruption': df.at[individual_id, 'la_placental_abruption'] = True - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'placental_abruption', - 'timing': 'intrapartum'}) + + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['placental_abruption'] += 1 elif complication == 'antepartum_haem': random_choice = self.rng.choice(['mild_moderate', 'severe'], @@ -1305,33 +1301,29 @@ def set_intrapartum_complications(self, individual_id, complication): if random_choice != 'severe': pregnancy_helper_functions.store_dalys_in_mni(individual_id, mni, 'mild_mod_aph_onset', self.sim.date) - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'mild_mod_antepartum_haemorrhage', - 'timing': 'intrapartum'}) + + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['mild_mod_antepartum_haemorrhage'] += 1 else: pregnancy_helper_functions.store_dalys_in_mni(individual_id, mni, 'severe_aph_onset', self.sim.date) - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'severe_antepartum_haemorrhage', - 'timing': 'intrapartum'}) + + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['severe_antepartum_haemorrhage'] += 1 elif complication == 'sepsis_chorioamnionitis': df.at[individual_id, 'la_sepsis'] = True mni[individual_id]['chorio_in_preg'] = True pregnancy_helper_functions.store_dalys_in_mni(individual_id, mni, 'sepsis_onset', self.sim.date) - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'sepsis', - 'timing': 'intrapartum'}) + + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['sepsis_intrapartum'] += 1 elif complication == 'uterine_rupture': df.at[individual_id, 'la_uterine_rupture'] = True pregnancy_helper_functions.store_dalys_in_mni(individual_id, mni, f'{complication}_onset', self.sim.date) - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'uterine_rupture', - 'timing': 'intrapartum'}) + + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['uterine_rupture'] += 1 def set_postpartum_complications(self, individual_id, complication): """ @@ -1384,9 +1376,7 @@ def set_postpartum_complications(self, individual_id, complication): # Set primary complication to true df.at[individual_id, 'la_postpartum_haem'] = True - logger_pn.info(key='maternal_complication', data={'person': individual_id, - 'type': f'{complication}', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[complication] += 1 # Store mni variables used during treatment if complication == 'pph_uterine_atony': @@ -1454,9 +1444,7 @@ def progression_of_hypertensive_disorders(self, individual_id, property_prefix): pregnancy_helper_functions.store_dalys_in_mni(individual_id, mni, 'eclampsia_onset', self.sim.date) - current_log.info(key='maternal_complication', data={'person': individual_id, - 'type': 'eclampsia', - 'timing': timing}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['eclampsia'] +=1 # Or from mild to severe gestational hypertension, risk reduced by treatment if df.at[individual_id, f'{property_prefix}_htn_disorders'] == 'gest_htn': @@ -1469,9 +1457,7 @@ def progression_of_hypertensive_disorders(self, individual_id, property_prefix): if risk_prog_gh_sgh > self.rng.random_sample(): df.at[individual_id, f'{property_prefix}_htn_disorders'] = 'severe_gest_htn' - current_log.info(key='maternal_complication', data={'person': individual_id, - 'type': 'severe_gest_htn', - 'timing': timing}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['severe_gest_htn'] +=1 # Or from severe gestational hypertension to severe pre-eclampsia... if df.at[individual_id, f'{property_prefix}_htn_disorders'] == 'severe_gest_htn': @@ -1479,9 +1465,7 @@ def progression_of_hypertensive_disorders(self, individual_id, property_prefix): df.at[individual_id, f'{property_prefix}_htn_disorders'] = 'severe_pre_eclamp' mni[individual_id]['new_onset_spe'] = True - current_log.info(key='maternal_complication', data={'person': individual_id, - 'type': 'severe_pre_eclamp', - 'timing': timing}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['severe_pre_eclamp'] +=1 # Or from mild pre-eclampsia to severe pre-eclampsia... if df.at[individual_id, f'{property_prefix}_htn_disorders'] == 'mild_pre_eclamp': @@ -1489,9 +1473,7 @@ def progression_of_hypertensive_disorders(self, individual_id, property_prefix): df.at[individual_id, f'{property_prefix}_htn_disorders'] = 'severe_pre_eclamp' mni[individual_id]['new_onset_spe'] = True - current_log.info(key='maternal_complication', data={'person': individual_id, - 'type': 'severe_pre_eclamp', - 'timing': timing}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['severe_pre_eclamp'] +=1 def apply_risk_of_early_postpartum_death(self, individual_id): """ @@ -1519,9 +1501,12 @@ def apply_risk_of_early_postpartum_death(self, individual_id): # If a cause is returned death is scheduled if potential_cause_of_death: pregnancy_helper_functions.log_mni_for_maternal_death(self, individual_id) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['direct_mat_death'] += 1 + self.sim.modules['Demography'].do_death(individual_id=individual_id, cause=potential_cause_of_death, originating_module=self.sim.modules['Labour']) + # If she hasn't died from any complications, we reset some key properties that resolve after risk of death # has been applied else: @@ -1653,11 +1638,7 @@ def prophylactic_labour_interventions(self, hsi_event): params = self.current_parameters mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info person_id = hsi_event.target - - # params['allowed_interventions'] contains a list of interventions delivered in this module. Removal of - # interventions from this list within test/analysis will stop this intervention from running - if 'prophylactic_labour_interventions' not in params['allowed_interventions']: - return + deliv_location = 'hc' if hsi_event.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' # We determine if the HCW will administer antibiotics for women with premature rupture of membranes if df.at[person_id, 'ps_premature_rupture_of_membranes']: @@ -1668,20 +1649,28 @@ def prophylactic_labour_interventions(self, hsi_event): mni[person_id]['abx_for_prom_given'] = True else: - # Run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='iv_abx', - hsi_event=hsi_event) - - # If she has not already receive antibiotics, we check for consumables - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + # # Run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='iv_abx', + # hsi_event=hsi_event) + # + # # If she has not already receive antibiotics, we check for consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['abx_for_prom'], + # opt_cons=self.item_codes_lab_consumables['iv_drug_equipment']) + # + # # Then query if these consumables are available during this HSI And provide if available. + # # Antibiotics for from reduce risk of newborn sepsis within the first + # # week of life + # if avail and sf_check: + + abx_prom_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='abx_for_prom', hsi_event=hsi_event, + q_param=[params['prob_hcw_avail_iv_abx'], params[f'mean_hcw_competence_{deliv_location}']], cons=self.item_codes_lab_consumables['abx_for_prom'], opt_cons=self.item_codes_lab_consumables['iv_drug_equipment']) - # Then query if these consumables are available during this HSI And provide if available. - # Antibiotics for from reduce risk of newborn sepsis within the first - # week of life - if avail and sf_check: + if abx_prom_delivered: mni[person_id]['abx_for_prom_given'] = True # ------------------------------ STEROIDS FOR PRETERM LABOUR ------------------------------- @@ -1689,14 +1678,22 @@ def prophylactic_labour_interventions(self, hsi_event): if mni[person_id]['labour_state'] == 'early_preterm_labour' or \ mni[person_id]['labour_state'] == 'late_preterm_labour': - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['antenatal_steroids'], + # opt_cons=self.item_codes_lab_consumables['iv_drug_equipment']) + # + # # If available they are given. Antenatal steroids reduce a preterm newborns chance of developing + # # respiratory distress syndrome and of death associated with prematurity + # if avail: + + steroids_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='antenatal_corticosteroids', hsi_event=hsi_event, + q_param=None, cons=self.item_codes_lab_consumables['antenatal_steroids'], opt_cons=self.item_codes_lab_consumables['iv_drug_equipment']) - # If available they are given. Antenatal steroids reduce a preterm newborns chance of developing - # respiratory distress syndrome and of death associated with prematurity - if avail: + if steroids_delivered: mni[person_id]['corticosteroids_given'] = True def determine_delivery_mode_in_spe_or_ec(self, person_id, hsi_event, complication): @@ -1735,12 +1732,10 @@ def assessment_and_treatment_of_severe_pre_eclampsia_mgso4(self, hsi_event, labo df = self.sim.population.props params = self.current_parameters person_id = hsi_event.target + deliv_location = 'hc' if hsi_event.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' # Women who have been admitted for delivery due to severe pre-eclampsia AND have already received magnesium # before moving to the labour ward do not receive the intervention again - if 'assessment_and_treatment_of_severe_pre_eclampsia' not in params['allowed_interventions']: - return - if (df.at[person_id, 'ps_htn_disorders'] == 'severe_pre_eclamp') or \ (df.at[person_id, 'pn_htn_disorders'] == 'severe_pre_eclamp'): @@ -1748,19 +1743,27 @@ def assessment_and_treatment_of_severe_pre_eclampsia_mgso4(self, hsi_event, labo if (df.at[person_id, 'ac_admitted_for_immediate_delivery'] == 'none') and (labour_stage == 'ip'): self.determine_delivery_mode_in_spe_or_ec(person_id, hsi_event, 'spe') - # Run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='anticonvulsant', - hsi_event=hsi_event) - - # Define and check for the required consumables - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + # # Run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='anticonvulsant', + # hsi_event=hsi_event) + # + # # Define and check for the required consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['magnesium_sulfate'], + # opt_cons=self.item_codes_lab_consumables['eclampsia_management_optional']) + # + # # If the consumables are available - the intervention is delivered. IV magnesium reduces the + # # probability that a woman with severe pre-eclampsia will experience eclampsia in labour + # if avail and sf_check: + + mag_sulph_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='mgso4', hsi_event=hsi_event, + q_param=[params['prob_hcw_avail_anticonvulsant'], params[f'mean_hcw_competence_{deliv_location}']], cons=self.item_codes_lab_consumables['magnesium_sulfate'], opt_cons=self.item_codes_lab_consumables['eclampsia_management_optional']) - # If the consumables are available - the intervention is delivered. IV magnesium reduces the - # probability that a woman with severe pre-eclampsia will experience eclampsia in labour - if avail and sf_check: + if mag_sulph_delivered: df.at[person_id, 'la_severe_pre_eclampsia_treatment'] = True pregnancy_helper_functions.log_met_need(self, 'mag_sulph', hsi_event) @@ -1776,22 +1779,26 @@ def assessment_and_treatment_of_hypertension(self, hsi_event, labour_stage): person_id = hsi_event.target params = self.current_parameters - # If the treatment is not allowed to be delivered or it has already been delivered the function won't run - if 'assessment_and_treatment_of_hypertension' not in params['allowed_interventions']: - return - + # If the treatment has already been delivered the function won't run if (df.at[person_id, 'ps_htn_disorders'] != 'none') or (df.at[person_id, 'pn_htn_disorders'] != 'none'): - # Then query if these consumables are available during this HSI - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, - cons=self.item_codes_lab_consumables['iv_antihypertensives'], + # # Then query if these consumables are available during this HSI + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['iv_antihypertensives'], + # opt_cons=self.item_codes_lab_consumables['iv_drug_equipment']) + # + # # If they are available then the woman is started on treatment. Intravenous antihypertensive reduce a + # # womans risk of progression from mild to severe gestational hypertension ANd reduce risk of death for + # # women with severe pre-eclampsia and eclampsia + # if avail: + + iv_anti_htns_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='oral_antihypertensives', hsi_event=hsi_event, + q_param=None, cons=self.item_codes_lab_consumables['iv_antihypertensives'], opt_cons=self.item_codes_lab_consumables['iv_drug_equipment']) - # If they are available then the woman is started on treatment. Intravenous antihypertensive reduce a - # womans risk of progression from mild to severe gestational hypertension ANd reduce risk of death for - # women with severe pre-eclampsia and eclampsia - if avail: + if iv_anti_htns_delivered: df.at[person_id, 'la_maternal_hypertension_treatment'] = True pregnancy_helper_functions.log_met_need(self, 'iv_htns', hsi_event) @@ -1802,9 +1809,16 @@ def assessment_and_treatment_of_hypertension(self, hsi_event, labour_stage): dose = (7 * 4) * 6 # approximating 4 tablets a day, for 6 weeks cons = {_i: dose for _i in self.item_codes_lab_consumables['oral_antihypertensives']} - avail = hsi_event.get_consumables(item_codes=cons) - if avail: + oral_anti_htns_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='oral_antihypertensives', hsi_event=hsi_event, + q_param=None, cons=cons) + + # avail = hsi_event.get_consumables(item_codes=cons) + + # if avail: + + if oral_anti_htns_delivered: df.at[person_id, 'la_gest_htn_on_treatment'] = True def assessment_and_treatment_of_eclampsia(self, hsi_event, labour_stage): @@ -1820,27 +1834,32 @@ def assessment_and_treatment_of_eclampsia(self, hsi_event, labour_stage): df = self.sim.population.props person_id = hsi_event.target params = self.current_parameters - - if 'assessment_and_treatment_of_eclampsia' not in params['allowed_interventions']: - return + deliv_location = 'hc' if hsi_event.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' if (df.at[person_id, 'ps_htn_disorders'] == 'eclampsia') or \ (df.at[person_id, 'pn_htn_disorders'] == 'eclampsia'): - # Run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='anticonvulsant', - hsi_event=hsi_event) - - # define and check required consumables - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + # # Run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='anticonvulsant', + # hsi_event=hsi_event) + # + # # define and check required consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['magnesium_sulfate'], + # opt_cons=self.item_codes_lab_consumables['eclampsia_management_optional']) + + mag_sulph_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='mgso4', hsi_event=hsi_event, + q_param=[params['prob_hcw_avail_anticonvulsant'], params[f'mean_hcw_competence_{deliv_location}']], cons=self.item_codes_lab_consumables['magnesium_sulfate'], opt_cons=self.item_codes_lab_consumables['eclampsia_management_optional']) if (labour_stage == 'ip') and (df.at[person_id, 'ac_admitted_for_immediate_delivery'] == 'none'): self.determine_delivery_mode_in_spe_or_ec(person_id, hsi_event, 'ec') - if avail and sf_check: + # if avail and sf_check: + if mag_sulph_delivered: # Treatment with magnesium reduces a womans risk of death from eclampsia df.at[person_id, 'la_eclampsia_treatment'] = True pregnancy_helper_functions.log_met_need(self, 'mag_sulph', hsi_event) @@ -1858,6 +1877,7 @@ def assessment_for_assisted_vaginal_delivery(self, hsi_event, indication): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info params = self.current_parameters person_id = hsi_event.target + deliv_location = 'hc' if hsi_event.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' def refer_for_cs(): if not indication == 'other': @@ -1868,8 +1888,7 @@ def refer_for_cs(): elif indication == 'ol': mni[person_id]['cs_indication'] = indication - if ('assessment_and_treatment_of_obstructed_labour' not in params['allowed_interventions']) or \ - (df.at[person_id, 'ac_admitted_for_immediate_delivery'] == 'caesarean_now') or \ + if (df.at[person_id, 'ac_admitted_for_immediate_delivery'] == 'caesarean_now') or \ (df.at[person_id, 'ac_admitted_for_immediate_delivery'] == 'caesarean_future'): return @@ -1878,20 +1897,29 @@ def refer_for_cs(): # We assume women with CPD cannot be delivered via AVD and will require a caesarean if not mni[person_id]['cpd']: - # If the general package is available AND the facility has the correct tools to carry out the - # delivery then it can occur - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + # # If the general package is available AND the facility has the correct tools to carry out the + # # delivery then it can occur + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['vacuum'], + # opt_cons=self.item_codes_lab_consumables['obstructed_labour']) + # + # # run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='avd', + # hsi_event=hsi_event) + + # if avail and sf_check: + + avd_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='avd', hsi_event=hsi_event, + q_param=[params['prob_hcw_avail_avd'], params[f'mean_hcw_competence_{deliv_location}']], cons=self.item_codes_lab_consumables['vacuum'], - opt_cons=self.item_codes_lab_consumables['obstructed_labour']) - - # run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='avd', - hsi_event=hsi_event) + opt_cons=self.item_codes_lab_consumables['obstructed_labour'], + equipment={'Delivery Forceps', 'Vacuum extractor'}) - if avail and sf_check: + if avd_delivered: # Add used equipment - hsi_event.add_equipment({'Delivery Forceps', 'Vacuum extractor'}) + # hsi_event.add_equipment({'Delivery Forceps', 'Vacuum extractor'}) pregnancy_helper_functions.log_met_need(self, f'avd_{indication}', hsi_event) @@ -1922,9 +1950,7 @@ def assessment_and_treatment_of_maternal_sepsis(self, hsi_event, labour_stage): df = self.sim.population.props params = self.current_parameters person_id = hsi_event.target - - if 'assessment_and_treatment_of_maternal_sepsis' not in params['allowed_interventions']: - return + deliv_location = 'hc' if hsi_event.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' if ( df.at[person_id, 'la_sepsis'] or @@ -1932,18 +1958,26 @@ def assessment_and_treatment_of_maternal_sepsis(self, hsi_event, labour_stage): ((labour_stage == 'ip') and df.at[person_id, 'ps_chorioamnionitis']) or (labour_stage == 'pp' and df.at[person_id, 'pn_sepsis_late_postpartum'])): - # run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='iv_abx', - hsi_event=hsi_event) - - # Define and check available consumables - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + # # run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='iv_abx', + # hsi_event=hsi_event) + # + # # Define and check available consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['maternal_sepsis_core'], + # opt_cons=self.item_codes_lab_consumables['maternal_sepsis_optional']) + # + # # If delivered this intervention reduces a womans risk of dying from sepsis + # if avail and sf_check: + + sepsis_treatment_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='sepsis_treatment', hsi_event=hsi_event, + q_param=[params['prob_hcw_avail_iv_abx'], params[f'mean_hcw_competence_{deliv_location}']], cons=self.item_codes_lab_consumables['maternal_sepsis_core'], opt_cons=self.item_codes_lab_consumables['maternal_sepsis_optional']) - # If delivered this intervention reduces a womans risk of dying from sepsis - if avail and sf_check: + if sepsis_treatment_delivered: df.at[person_id, 'la_sepsis_treatment'] = True pregnancy_helper_functions.log_met_need(self, 'sepsis_abx', hsi_event) @@ -1956,13 +1990,9 @@ def assessment_and_plan_for_antepartum_haemorrhage(self, hsi_event): (STR) 'hc' == health centre, 'hp' == hospital """ df = self.sim.population.props - params = self.current_parameters mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info person_id = hsi_event.target - if 'assessment_and_plan_for_referral_antepartum_haemorrhage' not in params['allowed_interventions']: - return - # We assume that any woman who has been referred from antenatal inpatient care due to haemorrhage are # automatically scheduled for blood transfusion if (df.at[person_id, 'ps_antepartum_haemorrhage'] != 'none') and (df.at[person_id, @@ -1988,13 +2018,9 @@ def assessment_for_referral_uterine_rupture(self, hsi_event): (STR) 'hc' == health centre, 'hp' == hospital """ df = self.sim.population.props - params = self.current_parameters mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info person_id = hsi_event.target - if 'assessment_and_plan_for_referral_uterine_rupture' not in params['allowed_interventions']: - return - # When uterine rupture is present, the mni dictionary is updated to allow treatment to be scheduled if df.at[person_id, 'la_uterine_rupture']: mni[person_id]['referred_for_surgery'] = True @@ -2012,23 +2038,29 @@ def active_management_of_the_third_stage_of_labour(self, hsi_event): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info params = self.current_parameters person_id = hsi_event.target - - if 'active_management_of_the_third_stage_of_labour' not in params['allowed_interventions']: - return - - # Define and check available consumables - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + deliv_location = 'hc' if hsi_event.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' + + # # Define and check available consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['amtsl'], + # opt_cons=self.item_codes_lab_consumables['iv_drug_equipment']) + # + # # run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='uterotonic', + # hsi_event=hsi_event) + # + # # This treatment reduces a womans risk of developing uterine atony AND retained placenta, both of which are + # # preceding causes of postpartum haemorrhage + # if avail and sf_check: + + amtsl_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='amtsl', hsi_event=hsi_event, + q_param=[params['prob_hcw_avail_uterotonic'], params[f'mean_hcw_competence_{deliv_location}']], cons=self.item_codes_lab_consumables['amtsl'], opt_cons=self.item_codes_lab_consumables['iv_drug_equipment']) - # run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='uterotonic', - hsi_event=hsi_event) - - # This treatment reduces a womans risk of developing uterine atony AND retained placenta, both of which are - # preceding causes of postpartum haemorrhage - if avail and sf_check: + if amtsl_delivered: mni[person_id]['amtsl_given'] = True def assessment_and_treatment_of_pph_uterine_atony(self, hsi_event): @@ -2043,23 +2075,29 @@ def assessment_and_treatment_of_pph_uterine_atony(self, hsi_event): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info params = self.current_parameters person_id = hsi_event.target - - if 'assessment_and_treatment_of_pph_uterine_atony' not in params['allowed_interventions']: - return + deliv_location = 'hc' if hsi_event.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' if df.at[person_id, 'la_postpartum_haem'] and not mni[person_id]['retained_placenta']: - # Define and check available consumables - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + # # Define and check available consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['pph_core'], + # opt_cons=self.item_codes_lab_consumables['pph_optional']) + # + # # run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='uterotonic', + # hsi_event=hsi_event) + # + # if avail and sf_check: + + pph_treatment_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='pph_treatment_uterotonics', hsi_event=hsi_event, + q_param=[params['prob_hcw_avail_uterotonic'], params[f'mean_hcw_competence_{deliv_location}']], cons=self.item_codes_lab_consumables['pph_core'], opt_cons=self.item_codes_lab_consumables['pph_optional']) - # run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='uterotonic', - hsi_event=hsi_event) - - if avail and sf_check: + if pph_treatment_delivered: pregnancy_helper_functions.log_met_need(self, 'uterotonics', hsi_event) # We apply a probability that this treatment will stop a womans bleeding in the first instance @@ -2087,26 +2125,32 @@ def assessment_and_treatment_of_pph_retained_placenta(self, hsi_event): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info params = self.current_parameters person_id = hsi_event.target - - if 'assessment_and_treatment_of_pph_retained_placenta' not in params['allowed_interventions']: - return + deliv_location = 'hc' if hsi_event.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' if ( (df.at[person_id, 'la_postpartum_haem'] and mni[person_id]['retained_placenta']) or df.at[person_id, 'pn_postpartum_haem_secondary'] ): - # Log the consumables but dont condition the treatment on their availability - the primary mechanism of this - # intervention doesnt require consumables - hsi_event.get_consumables(item_codes=self.item_codes_lab_consumables['pph_optional']) - - # run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='man_r_placenta', - hsi_event=hsi_event) + # # Log the consumables but dont condition the treatment on their availability - the primary mechanism of this + # # intervention doesnt require consumables + # hsi_event.get_consumables(item_codes=self.item_codes_lab_consumables['pph_optional']) + # + # # run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='man_r_placenta', + # hsi_event=hsi_event) + # + # # Similar to uterotonics we apply a probability that this intervention will successfully stop + # # bleeding to ensure some women go on to require further care + # if sf_check: + + pph_mrrp_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='pph_treatment_mrrp', hsi_event=hsi_event, + q_param=[params['prob_hcw_avail_man_r_placenta'], params[f'mean_hcw_competence_{deliv_location}']], + cons=None, + opt_cons=self.item_codes_lab_consumables['pph_optional']) - # Similar to uterotonics we apply a probability that this intervention will successfully stop - # bleeding to ensure some women go on to require further care - if sf_check: + if pph_mrrp_delivered: pregnancy_helper_functions.log_met_need(self, 'man_r_placenta', hsi_event) if params['prob_successful_manual_removal_placenta'] > self.rng.random_sample(): @@ -2132,22 +2176,31 @@ def surgical_management_of_pph(self, hsi_event): person_id = hsi_event.target df = self.sim.population.props params = self.current_parameters - - # We log the required consumables and condition the surgery happening on the availability of the - # first consumable in this package, the anaesthetic required for the surgery - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + deliv_location = 'hc' if hsi_event.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' + + # # We log the required consumables and condition the surgery happening on the availability of the + # # first consumable in this package, the anaesthetic required for the surgery + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['obstetric_surgery_core'], + # opt_cons=self.item_codes_lab_consumables['obstetric_surgery_optional']) + # + # # run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='surg', + # hsi_event=hsi_event) + # + # if avail and sf_check: + # # Add used equipment + # hsi_event.add_equipment(hsi_event.healthcare_system.equipment.from_pkg_names('Major Surgery')) + + pph_surg_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='pph_treatment_surg', hsi_event=hsi_event, + q_param=[params['prob_hcw_avail_surg'], params[f'mean_hcw_competence_{deliv_location}']], cons=self.item_codes_lab_consumables['obstetric_surgery_core'], - opt_cons=self.item_codes_lab_consumables['obstetric_surgery_optional']) - - # run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='surg', - hsi_event=hsi_event) - - if avail and sf_check: - # Add used equipment - hsi_event.add_equipment(hsi_event.healthcare_system.equipment.from_pkg_names('Major Surgery')) + opt_cons=self.item_codes_lab_consumables['obstetric_surgery_optional'], + equipment=hsi_event.healthcare_system.equipment.from_pkg_names('Major Surgery')) + if pph_surg_delivered: # determine if uterine preserving surgery will be successful treatment_success_pph = params['success_rate_pph_surgery'] > self.rng.random_sample() @@ -2174,20 +2227,29 @@ def blood_transfusion(self, hsi_event): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info params = self.current_parameters df = self.sim.population.props - - # Check consumables - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + deliv_location = 'hc' if hsi_event.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' + + # # Check consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_lab_consumables['blood_transfusion'], + # opt_cons=self.item_codes_lab_consumables['iv_drug_equipment']) + # + # # check HCW + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='blood_tran', + # hsi_event=hsi_event) + # + # if avail and sf_check: + # hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) + + blood_transfusion_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='blood_transfusion', hsi_event=hsi_event, + q_param=[params['prob_hcw_avail_blood_tran'], params[f'mean_hcw_competence_{deliv_location}']], cons=self.item_codes_lab_consumables['blood_transfusion'], - opt_cons=self.item_codes_lab_consumables['iv_drug_equipment']) - - # check HCW - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='blood_tran', - hsi_event=hsi_event) - - if avail and sf_check: - hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) + opt_cons=self.item_codes_lab_consumables['blood_test_equipment'], + equipment={'Drip stand', 'Infusion pump'}) + if blood_transfusion_delivered: mni[person_id]['received_blood_transfusion'] = True pregnancy_helper_functions.log_met_need(self, 'blood_tran', hsi_event) @@ -2211,27 +2273,41 @@ def assessment_and_treatment_of_anaemia(self, hsi_event): mother = df.loc[person_id] mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info - # Add used equipment - hsi_event.add_equipment({'Analyser, Haematology'}) - - # Use dx_test function to assess anaemia status - test_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( - dx_tests_to_run='full_blood_count_hb_pn', hsi_event=hsi_event) - - hsi_event.get_consumables(item_codes=self.item_codes_lab_consumables['blood_test_equipment']) - - # Check consumables - if test_result: + # # Add used equipment + # hsi_event.add_equipment({'Analyser, Haematology'}) + # + # # Use dx_test function to assess anaemia status + # test_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( + # dx_tests_to_run='full_blood_count_hb_pn', hsi_event=hsi_event) + # + # hsi_event.get_consumables(item_codes=self.item_codes_lab_consumables['blood_test_equipment']) + # + # # Check consumables + # if test_result: + + full_blood_count_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='full_blood_count', hsi_event=hsi_event, + q_param=None, cons=None, + opt_cons=self.item_codes_lab_consumables['blood_test_equipment'], + equipment={'Analyser, Haematology'}, dx_test='full_blood_count_hb_pn') + + if full_blood_count_delivered: # Start iron and folic acid supplementation for women not already receiving this if not mother.la_iron_folic_acid_postnatal: days = int((6 - df.at[person_id, 'pn_postnatal_period_in_weeks']) * 7) dose = days * 3 cons = {_i: dose for _i in self.item_codes_lab_consumables['iron_folic_acid']} - avail = hsi_event.get_consumables(item_codes=cons) + # avail = hsi_event.get_consumables(item_codes=cons) + # + # # Start iron and folic acid treatment + # if avail: + + iron_folic_acid_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='iron_folic_acid', + hsi_event=hsi_event, q_param=None, cons=cons) - # Start iron and folic acid treatment - if avail: + if iron_folic_acid_delivered: df.at[person_id, 'la_iron_folic_acid_postnatal'] = True if self.rng.random_sample() < params['effect_of_ifa_for_resolving_anaemia']: @@ -2277,11 +2353,16 @@ def interventions_delivered_pre_discharge(self, hsi_event): # ------------------------------- Postnatal iron and folic acid --------------------------------------------- cons = {_i: params['number_ifa_tablets_required_postnatally'] for _i in self.item_codes_lab_consumables['iron_folic_acid']} - avail = hsi_event.get_consumables(item_codes=cons) + + iron_folic_acid_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='iron_folic_acid', + hsi_event=hsi_event, q_param=None, cons=cons) + + # avail = hsi_event.get_consumables(item_codes=cons) # Women are started on iron and folic acid for the next three months which reduces risk of anaemia in the # postnatal period - if avail and (self.rng.random_sample() < params['prob_adherent_ifa']): + if iron_folic_acid_delivered and (self.rng.random_sample() < params['prob_adherent_ifa']): df.at[person_id, 'la_iron_folic_acid_postnatal'] = True def run_if_receives_skilled_birth_attendance_cant_run(self, hsi_event): @@ -2423,10 +2504,7 @@ def apply(self, individual_id): 'defining_term_status'][3]: mni[individual_id]['labour_state'] = 'early_preterm_labour' - - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'early_preterm_labour', - 'timing': 'intrapartum'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['early_preterm_labour'] += 1 elif params['list_limits_for_defining_term_status'][4] <= gestational_age_in_days <= params['list_limits' '_for_defining' @@ -2434,18 +2512,12 @@ def apply(self, individual_id): 'status'][5]: mni[individual_id]['labour_state'] = 'late_preterm_labour' - - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'late_preterm_labour', - 'timing': 'intrapartum'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['late_preterm_labour'] += 1 elif gestational_age_in_days >= params['list_limits_for_defining_term_status'][6]: mni[individual_id]['labour_state'] = 'postterm_labour' - - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'post_term_labour', - 'timing': 'intrapartum'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['post_term_labour'] += 1 labour_state = mni[individual_id]['labour_state'] @@ -2612,9 +2684,7 @@ def apply(self, individual_id): self.module.set_intrapartum_complications(individual_id, complication=complication) if df.at[individual_id, 'la_obstructed_labour']: - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'obstructed_labour', - 'timing': 'intrapartum'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['obstructed_labour'] += 1 # And we determine if any existing hypertensive disorders would worsen self.module.progression_of_hypertensive_disorders(individual_id, property_prefix='ps') @@ -2686,6 +2756,7 @@ def apply(self, individual_id): # If a cause is returned death is scheduled if potential_cause_of_death: pregnancy_helper_functions.log_mni_for_maternal_death(self.module, individual_id) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['direct_mat_death'] += 1 self.sim.modules['Demography'].do_death(individual_id=individual_id, cause=potential_cause_of_death, originating_module=self.sim.modules['Labour']) @@ -2729,8 +2800,7 @@ def apply(self, individual_id): del mni[individual_id] if df.at[individual_id, 'la_intrapartum_still_birth'] or mni[individual_id]['single_twin_still_birth']: - logger.info(key='intrapartum_stillbirth', data={'mother_id': individual_id, - 'date_of_ip_stillbirth': self.sim.date}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['intrapartum_stillbirth'] += 1 # Reset property if individual_id in mni: @@ -2818,13 +2888,10 @@ def apply(self, mother_id): self.module.set_postpartum_complications(mother_id, complication=complication) if df.at[mother_id, 'la_sepsis_pp']: - logger_pn.info(key='maternal_complication', data={'person': mother_id, - 'type': 'sepsis_postnatal', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['sepsis_postnatal'] += 1 + if df.at[mother_id, 'la_postpartum_haem']: - logger_pn.info(key='maternal_complication', data={'person': mother_id, - 'type': 'primary_postpartum_haemorrhage', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['primary_postpartum_haemorrhage'] += 1 self.module.progression_of_hypertensive_disorders(mother_id, property_prefix='pn') @@ -2908,6 +2975,7 @@ def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info df = self.sim.population.props params = self.module.current_parameters + deliv_location = 'hc' if self.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' if not df.at[person_id, 'is_alive']: return @@ -2934,21 +3002,28 @@ def apply(self, person_id, squeeze_factor): self.module.assessment_for_assisted_vaginal_delivery(self, indication='spe_ec') # LOG CONSUMABLES FOR DELIVERY... - # We assume all deliveries require this basic package of consumables - avail = pregnancy_helper_functions.return_cons_avail( - self.module, self, - cons=self.module.item_codes_lab_consumables['delivery_core'], + # # We assume all deliveries require this basic package of consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self.module, self, + # cons=self.module.item_codes_lab_consumables['delivery_core'], + # opt_cons=self.module.item_codes_lab_consumables['delivery_optional']) + # + # # If the clean delivery kit consumable is available, we assume women benefit from clean delivery + # if avail: + # mni[person_id]['clean_birth_practices'] = True + + birth_kit_used = pregnancy_helper_functions.check_int_deliverable( + self.module, int_name='birth_kit', + hsi_event=self, cons=self.module.item_codes_lab_consumables['delivery_core'], opt_cons=self.module.item_codes_lab_consumables['delivery_optional']) - # Add used equipment - self.add_equipment({'Delivery set', 'Weighing scale', 'Stethoscope, foetal, monaural, Pinard, plastic', - 'Resuscitaire', 'Sphygmomanometer', 'Tray, emergency', 'Suction machine', - 'Thermometer', 'Drip stand', 'Infusion pump'}) - - # If the clean delivery kit consumable is available, we assume women benefit from clean delivery - if avail: + if birth_kit_used: mni[person_id]['clean_birth_practices'] = True + # Add used equipment + self.add_equipment({'Delivery set', 'Weighing scale', 'Stethoscope, foetal, monaural, Pinard, plastic', + 'Resuscitaire', 'Sphygmomanometer', 'Tray, emergency', 'Suction machine', + 'Thermometer', 'Drip stand', 'Infusion pump'}) # ===================================== PROPHYLACTIC CARE =================================================== # The following function manages the consumables and administration of prophylactic interventions in labour # (clean delivery practice, antibiotics for PROM, steroids for preterm labour) @@ -2977,9 +3052,7 @@ def apply(self, person_id, squeeze_factor): self.module.progression_of_hypertensive_disorders(person_id, property_prefix='ps') if df.at[person_id, 'la_obstructed_labour']: - logger.info(key='maternal_complication', data={'person': person_id, - 'type': 'obstructed_labour', - 'timing': 'intrapartum'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['obstructed_labour'] += 1 # ======================================= COMPLICATION MANAGEMENT ========================== # Next, women in labour are assessed for complications and treatment delivered if a need is identified and @@ -3018,14 +3091,22 @@ def apply(self, person_id, squeeze_factor): if not mni[person_id]['sought_care_for_complication']: # TODO: potential issue is that this consumable is being logged now for every birth as opposed to # for each birth where resuscitation of the newborn is required - avail = pregnancy_helper_functions.return_cons_avail( - self.module, self, cons=self.module.item_codes_lab_consumables['resuscitation'], opt_cons=None) - - # Run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.module, - sf='neo_resus', - hsi_event=self) - if sf_check and avail: + + neo_resus_delivered = pregnancy_helper_functions.check_int_deliverable( + self.module, int_name='neo_resus', hsi_event=self, + q_param=[params['prob_hcw_avail_neo_resus'], params[f'mean_hcw_competence_{deliv_location}']], + cons=self.module.item_codes_lab_consumables['resuscitation']) + + # avail = pregnancy_helper_functions.return_cons_avail( + # self.module, self, cons=self.module.item_codes_lab_consumables['resuscitation'], opt_cons=None) + # + # # Run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.module, + # sf='neo_resus', + # hsi_event=self) + # if sf_check and avail: + + if neo_resus_delivered: mni[person_id]['neo_will_receive_resus_if_needed'] = True # ========================================== SCHEDULING CEMONC CARE ========================================= @@ -3226,6 +3307,7 @@ def apply(self, person_id, squeeze_factor): df = self.sim.population.props mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info params = self.module.current_parameters + deliv_location = 'hc' if self.ACCEPTED_FACILITY_LEVEL == '1a' else 'hp' # We use the variable self.timing to differentiate between women sent to this event during labour and women # sent after labour @@ -3235,22 +3317,30 @@ def apply(self, person_id, squeeze_factor): # delivered if mni[person_id]['referred_for_cs'] and self.timing == 'intrapartum': - # We log the required consumables and condition the caesarean happening on the availability of the - # first consumable in this package, the anaesthetic required for the surgery - avail = pregnancy_helper_functions.return_cons_avail( - self.module, self, + # # We log the required consumables and condition the caesarean happening on the availability of the + # # first consumable in this package, the anaesthetic required for the surgery + # avail = pregnancy_helper_functions.return_cons_avail( + # self.module, self, + # cons=self.module.item_codes_lab_consumables['caesarean_delivery_core'], + # opt_cons=self.module.item_codes_lab_consumables['caesarean_delivery_optional']) + # + # # We check that the HCW will deliver the intervention + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.module, sf='surg', + # hsi_event=self) + + cs_delivered = pregnancy_helper_functions.check_int_deliverable( + self.module, int_name='abx_for_prom', hsi_event=self, + q_param=[params['prob_hcw_avail_surg'], params[f'mean_hcw_competence_{deliv_location}']], cons=self.module.item_codes_lab_consumables['caesarean_delivery_core'], opt_cons=self.module.item_codes_lab_consumables['caesarean_delivery_optional']) - # We check that the HCW will deliver the intervention - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.module, sf='surg', - hsi_event=self) - # Block CS delivery for this analysis if params['la_analysis_in_progress'] and (params['cemonc_availability'] == 0.0): logger.debug(key='message', data="cs delivery blocked for this analysis") - elif (avail and sf_check) or (mni[person_id]['cs_indication'] == 'other'): + # elif (avail and sf_check) or (mni[person_id]['cs_indication'] == 'other'): + elif cs_delivered or (mni[person_id]['cs_indication'] == 'other'): + # If intervention is delivered - add used equipment self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) diff --git a/src/tlo/methods/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py new file mode 100644 index 0000000000..582855df5d --- /dev/null +++ b/src/tlo/methods/mnh_cohort_module.py @@ -0,0 +1,120 @@ +from pathlib import Path + +import numpy as np +import pandas as pd +from tlo import DateOffset, Module, Parameter, Property, Types, logging +from tlo.methods import Metadata +from tlo.analysis.utils import parse_log_file +from tlo.events import Event, IndividualScopeEventMixin + + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +class MaternalNewbornHealthCohort(Module): + """ + When registered this module overrides the population data frame with a cohort of pregnant women. Cohort properties + are sourced from a long run of the full model in which the properties of all newly pregnant women per year were + logged. The cohort represents women in 2024. The maximum population size is 13,000. + """ + + METADATA = { + Metadata.DISEASE_MODULE, + Metadata.USES_SYMPTOMMANAGER, + Metadata.USES_HEALTHSYSTEM, + Metadata.USES_HEALTHBURDEN + } + + CAUSES_OF_DEATH = {} + CAUSES_OF_DISABILITY = {} + PARAMETERS = {} + PROPERTIES = {} + + def __init__(self, name=None, resourcefilepath=None): + super().__init__(name) + self.resourcefilepath = resourcefilepath + + def read_parameters(self, data_folder): + pass + + def initialise_population(self, population): + """Set our property values for the initial population. + + This method is called by the simulation when creating the initial population, and is + responsible for assigning initial values, for every individual, of those properties + 'owned' by this module, i.e. those declared in the PROPERTIES dictionary above. + + :param population: the population of individuals + """ + + # log_file = parse_log_file( + # '/Users/j_collins/PycharmProjects/TLOmodel/resources/maternal cohort/' + # 'fullmodel_200k_cohort__2024-04-24T072516.log', + # level=logging.DEBUG)['tlo.methods.contraception'] + # + # all_pregnancies = log_file['properties_of_pregnant_person'].loc[ + # log_file['properties_of_pregnant_person'].date.dt.year == 2024].drop(columns=['date']) + # all_pregnancies.index = [x for x in range(len(all_pregnancies))] + + # Read in excel sheet with cohort + all_preg_df = pd.read_excel(Path(f'{self.resourcefilepath}/maternal cohort') / + 'ResourceFile_All2024PregnanciesCohortModel.xlsx') + + # Only select rows equal to the desired population size + preg_pop = all_preg_df.loc[0:(len(self.sim.population.props))-1] + + # Set the dtypes and index of the cohort dataframe + props_dtypes = self.sim.population.props.dtypes + preg_pop_final = preg_pop.astype(props_dtypes.to_dict()) + preg_pop_final.index.name = 'person' + + # For the below columns we manually overwrite the dtypes + for column in ['rt_injuries_for_minor_surgery', 'rt_injuries_for_major_surgery', + 'rt_injuries_to_heal_with_time', 'rt_injuries_for_open_fracture_treatment', + 'rt_injuries_left_untreated', 'rt_injuries_to_cast']: + preg_pop_final[column] = [[] for _ in range(len(preg_pop_final))] + + # Set the population.props dataframe to the new cohort + self.sim.population.props = preg_pop_final + + # Update key pregnancy properties + df = self.sim.population.props + population = df.loc[df.is_alive] + df.loc[population.index, 'date_of_last_pregnancy'] = self.sim.start_date + df.loc[population.index, 'co_contraception'] = "not_using" + + + def initialise_simulation(self, sim): + """Get ready for simulation start. + + This method is called just before the main simulation loop begins, and after all + modules have read their parameters and the initial population has been created. + It is a good place to add initial events to the event queue. + + """ + df = self.sim.population.props + + # Clear HSI queue for events scheduled during initialisation + sim.modules['HealthSystem'].HSI_EVENT_QUEUE.clear() + + # Clear the individual event queue for events scheduled during initialisation + updated_event_queue = [item for item in self.sim.event_queue.queue + if not isinstance(item[3], IndividualScopeEventMixin)] + self.sim.event_queue.queue = updated_event_queue + + # Prevent additional pregnancies from occurring during the cohort tun + self.sim.modules['Contraception'].processed_params['p_pregnancy_with_contraception_per_month'].iloc[:] = 0 + self.sim.modules['Contraception'].processed_params['p_pregnancy_no_contraception_per_month'].iloc[:] = 0 + + # Set labour date for cohort women + for person in df.index: + self.sim.modules['Labour'].set_date_of_labour(person) + + def on_birth(self, mother_id, child_id): + pass + + def report_daly_values(self): + pass + + def on_hsi_alert(self, person_id, treatment_id): + pass diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 3691bc6003..c1b34abae6 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -523,28 +523,23 @@ def apply_risk_of_congenital_anomaly(self, child_id): if self.rng.random_sample() < params['prob_congenital_heart_anomaly']: self.congeintal_anomalies.set(child_id, 'heart') - logger.info(key='newborn_complication', data={'newborn': child_id, - 'type': 'congenital_heart_anomaly'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['congenital_heart_anomaly'] += 1 if self.rng.random_sample() < params['prob_limb_musc_skeletal_anomaly']: self.congeintal_anomalies.set(child_id, 'limb_musc_skeletal') - logger.info(key='newborn_complication', data={'newborn': child_id, - 'type': 'limb_or_musculoskeletal_anomaly'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['limb_or_musculoskeletal_anomaly'] += 1 if self.rng.random_sample() < params['prob_urogenital_anomaly']: self.congeintal_anomalies.set(child_id, 'urogenital') - logger.info(key='newborn_complication', data={'newborn': child_id, - 'type': 'urogenital_anomaly'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['urogenital_anomaly'] += 1 if self.rng.random_sample() < params['prob_digestive_anomaly']: self.congeintal_anomalies.set(child_id, 'digestive') - logger.info(key='newborn_complication', data={'newborn': child_id, - 'type': 'digestive_anomaly'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['digestive_anomaly'] += 1 if self.rng.random_sample() < params['prob_other_anomaly']: self.congeintal_anomalies.set(child_id, 'other') - logger.info(key='newborn_complication', data={'newborn': child_id, - 'type': 'other_anomaly'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['other_anomaly'] += 1 def apply_risk_of_neonatal_infection_and_sepsis(self, child_id): """ @@ -558,9 +553,7 @@ def apply_risk_of_neonatal_infection_and_sepsis(self, child_id): # The linear model calculates the individuals probability of early_onset_neonatal_sepsis if self.eval(self.nb_linear_models['early_onset_neonatal_sepsis'], child_id): df.at[child_id, 'nb_early_onset_neonatal_sepsis'] = True - - logger.info(key='newborn_complication', data={'newborn': child_id, - 'type': 'early_onset_sepsis'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['early_onset_sepsis'] += 1 def apply_risk_of_encephalopathy(self, child_id, timing): """ @@ -592,8 +585,7 @@ def apply_risk_of_encephalopathy(self, child_id, timing): else: df.at[child_id, 'nb_encephalopathy'] = 'severe_enceph' - logger.info(key='newborn_complication', data={'newborn': child_id, - 'type': f'{df.at[child_id, "nb_encephalopathy"]}'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[f'{df.at[child_id, "nb_encephalopathy"]}'] += 1 # Check all encephalopathy cases receive a grade if df.at[child_id, 'nb_encephalopathy'] == 'none': @@ -616,9 +608,7 @@ def apply_risk_of_preterm_respiratory_distress_syndrome(self, child_id): # Use the linear model to calculate individual risk and make changes if self.eval(self.nb_linear_models['rds_preterm'], child_id): df.at[child_id, 'nb_preterm_respiratory_distress'] = True - - logger.info(key='newborn_complication', data={'newborn': child_id, - 'type': 'respiratory_distress_syndrome'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['respiratory_distress_syndrome'] += 1 def apply_risk_of_not_breathing_at_birth(self, child_id): """ @@ -638,9 +628,7 @@ def apply_risk_of_not_breathing_at_birth(self, child_id): # explicitly modelled elif self.rng.random_sample() < params['prob_failure_to_transition']: df.at[child_id, 'nb_not_breathing_at_birth'] = True - - logger.info(key='newborn_complication', data={'newborn': child_id, - 'type': 'not_breathing_at_birth'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['not_breathing_at_birth'] += 1 def scheduled_week_one_postnatal_event(self, individual_id): """ @@ -912,8 +900,15 @@ def kangaroo_mother_care(self, hsi_event): if (df.at[person_id, 'nb_low_birth_weight_status'] != 'normal_birth_weight') or \ (df.at[person_id, 'nb_low_birth_weight_status'] != 'macrosomia'): + kmc_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='kmc', + hsi_event=hsi_event, + q_param=[params['prob_kmc_available']], + cons=None) + # Check KMC can be delivered - if self.rng.random_sample() < params['prob_kmc_available']: + # if self.rng.random_sample() < params['prob_kmc_available']: + if kmc_delivered: # Store treatment as a property of the newborn used to apply treatment effect df.at[person_id, 'nb_kangaroo_mother_care'] = True @@ -964,6 +959,8 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): """ df = self.sim.population.props person_id = int(hsi_event.target) + l_params = self.sim.modules['Labour'].current_parameters + pnc_location = 'hc' if facility_type == '1a' else 'hp' # We assume that only hospitals are able to deliver full supportive care for neonatal sepsis, full supportive # care evokes a stronger treatment effect than injectable antibiotics alone @@ -971,35 +968,53 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): if df.at[person_id, 'nb_early_onset_neonatal_sepsis'] or df.at[person_id, 'pn_sepsis_late_neonatal'] or\ df.at[person_id, 'pn_sepsis_early_neonatal']: - # Run HCW check - sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], - sf='iv_abx', - hsi_event=hsi_event) + + # # Run HCW check + # sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], + # sf='iv_abx', + # hsi_event=hsi_event) if facility_type != '1a': - # check consumables - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + # # check consumables + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_nb_consumables['sepsis_supportive_care_core'], + # opt_cons=self.item_codes_nb_consumables['sepsis_supportive_care_optional']) + + # # Then, if the consumables are available, treatment for sepsis is delivered + # if avail and sf_check: + + neo_sepsis_treatment_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='neo_sepsis_treatment_supp_care', hsi_event=hsi_event, + q_param=[l_params['prob_hcw_avail_iv_abx'], l_params[f'mean_hcw_competence_{pnc_location}']], cons=self.item_codes_nb_consumables['sepsis_supportive_care_core'], - opt_cons=self.item_codes_nb_consumables['sepsis_supportive_care_optional']) + opt_cons=self.item_codes_nb_consumables['sepsis_supportive_care_optional'], + equipment={'Drip stand', 'Infusion pump'}) - # Then, if the consumables are available, treatment for sepsis is delivered - if avail and sf_check: + if neo_sepsis_treatment_delivered: df.at[person_id, 'nb_supp_care_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_supportive_care', hsi_event) - hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) + # hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) # The same pattern is then followed for health centre care else: - avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, + # cons=self.item_codes_nb_consumables['sepsis_abx'], + # opt_cons=self.item_codes_nb_consumables['iv_drug_equipment']) + # + # if avail and sf_check: + neo_sepsis_treatment_delivered = pregnancy_helper_functions.check_int_deliverable( + self, int_name='neo_sepsis_treatment_abx', hsi_event=hsi_event, + q_param=[l_params['prob_hcw_avail_iv_abx'], l_params[f'mean_hcw_competence_{pnc_location}']], cons=self.item_codes_nb_consumables['sepsis_abx'], - opt_cons=self.item_codes_nb_consumables['iv_drug_equipment']) + opt_cons=self.item_codes_nb_consumables['iv_drug_equipment'], + equipment={'Drip stand', 'Infusion pump'}) - if avail and sf_check: + if neo_sepsis_treatment_delivered: df.at[person_id, 'nb_inj_abx_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_abx', hsi_event) - hsi_event.add_equipment({'Drip stand', 'Infusion pump', 'Oxygen cylinder, with regulator'}) + # hsi_event.add_equipment({'Drip stand', 'Infusion pump', 'Oxygen cylinder, with regulator'}) def link_twins(self, child_one, child_two, mother_id): """ @@ -1021,6 +1036,7 @@ def link_twins(self, child_one, child_two, mother_id): 'date_of_delivery': self.sim.date} logger.info(key='twin_birth', data=twin_birth, description='A record of each birth of twin pairs') + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['twin_birth'] += 1 # Finally we log the second live birth and add another to the womans parity df.at[mother_id, 'la_parity'] += 1 @@ -1157,14 +1173,14 @@ def on_birth(self, mother_id, child_id): if (df.at[child_id, 'nb_low_birth_weight_status'] == 'low_birth_weight') or\ (df.at[child_id, 'nb_low_birth_weight_status'] == 'very_low_birth_weight') or\ (df.at[child_id, 'nb_low_birth_weight_status'] == 'extremely_low_birth_weight'): - logger.info(key='newborn_complication', data={'newborn': child_id, 'type': 'low_birth_weight'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['low_birth_weight'] += 1 elif df.at[child_id, 'nb_low_birth_weight_status'] == 'macrosomia': - logger.info(key='newborn_complication', data={'newborn': child_id, 'type': 'macrosomia'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['macrosomia'] += 1 df.at[child_id, 'nb_size_for_gestational_age'] = mni[mother_id]['birth_size'] if df.at[child_id, 'nb_size_for_gestational_age'] == 'small_for_gestational_age': - logger.info(key='newborn_complication', data={'newborn': child_id, 'type': 'small_for_gestational_age'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['small_for_gestational_age'] += 1 df.at[child_id, 'nb_early_init_breastfeeding'] = False df.at[child_id, 'nb_breastfeeding_status'] = 'none' diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 25bce6013f..e0f6708fd5 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -354,10 +354,7 @@ def further_on_birth_postnatal_supervisor(self, mother_id): # Store the onset weight for daly calculations store_dalys_in_mni(mother_id, mni, f'{fistula_type}_fistula_onset', self.sim.date) - - logger.info(key='maternal_complication', data={'person': mother_id, - 'type': f'{fistula_type}_fistula', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[f'{fistula_type}_fistula'] += 1 # Determine if she will seek care for repair care_seeking_for_repair = self.pn_linear_models[ @@ -500,9 +497,7 @@ def onset(eq): for person in new_sepsis.loc[new_sepsis].index: store_dalys_in_mni(person, mni, 'sepsis_onset', self.sim.date) - logger.info(key='maternal_complication', data={'person': person, - 'type': 'sepsis', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['sepsis_postnatal'] += 1 # ------------------------------------ SECONDARY PPH ---------------------------------------------------------- # Next we determine if any women will experience postnatal bleeding @@ -514,10 +509,7 @@ def onset(eq): for person in onset_pph.loc[onset_pph].index: store_dalys_in_mni(person, mni, 'secondary_pph_onset', self.sim.date) - - logger.info(key='maternal_complication', data={'person': person, - 'type': 'secondary_postpartum_haemorrhage', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['secondary_postpartum_haemorrhage'] += 1 # --------------------------------------------- ANAEMIA -------------------------------------------------- # We apply a risk of anaemia developing in this week, and determine its severity @@ -533,11 +525,6 @@ def onset(eq): store_dalys_in_mni(person, mni, f'{df.at[person, "pn_anaemia_following_pregnancy"]}_anaemia_pp_onset', self.sim.date) - logger.info(key='maternal_complication', data={'person': person, - 'type': f'{df.at[person, "pn_anaemia_following_pregnancy"]}' - f'_anaemia', - 'timing': 'postnatal'}) - # --------------------------------------- HYPERTENSION ------------------------------------------ # For women who are still experiencing a hypertensive disorder of pregnancy we determine if that will now # resolve @@ -594,9 +581,7 @@ def log_new_progressed_cases(disease): mni=mni, mni_variable='eclampsia_onset', date=self.sim.date) for person in new_onset_disease.index: - logger.info(key='maternal_complication', data={'person': person, - 'type': disease, - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[disease] += 1 if disease == 'severe_pre_eclamp': mni[person]['new_onset_spe'] = True @@ -637,11 +622,8 @@ def log_new_progressed_cases(disease): df.loc[pre_eclampsia.loc[pre_eclampsia].index, 'ps_prev_pre_eclamp'] = True df.loc[pre_eclampsia.loc[pre_eclampsia].index, 'pn_htn_disorders'] = 'mild_pre_eclamp' - - for person in pre_eclampsia.loc[pre_eclampsia].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'mild_pre_eclamp', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[ + 'mild_pre_eclamp'] += (len(pre_eclampsia.loc[pre_eclampsia].index)) # -------------------------------- RISK OF GESTATIONAL HYPERTENSION -------------------------------------- gest_hypertension = self.apply_linear_model( @@ -650,11 +632,8 @@ def log_new_progressed_cases(disease): (df['pn_htn_disorders'] == 'none')]) df.loc[gest_hypertension.loc[gest_hypertension].index, 'pn_htn_disorders'] = 'gest_htn' - - for person in gest_hypertension.loc[gest_hypertension].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'mild_gest_htn', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[ + 'mild_gest_htn'] += len(gest_hypertension.loc[gest_hypertension].index) # -------------------------------- RISK OF DEATH SEVERE HYPERTENSION ------------------------------------------ # Risk of death is applied to women with severe hypertensive disease @@ -667,13 +646,14 @@ def log_new_progressed_cases(disease): # Those women who die the on_death function in demography is applied for person in die_from_htn.loc[die_from_htn].index: + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['severe_gestational_hypertension_m_death'] += 1 self.sim.modules['Demography'].do_death(individual_id=person, cause='severe_gestational_hypertension', originating_module=self.sim.modules['PostnatalSupervisor']) del self.sim.modules['PregnancySupervisor'].mother_and_newborn_info[person] # ----------------------------------------- CARE SEEKING ------------------------------------------------------ - # We now use the the pn_emergency_event_mother property that has just been set for women who are experiencing + # We now use the pn_emergency_event_mother property that has just been set for women who are experiencing # severe complications to select a subset of women who may choose to seek care can_seek_care = df.loc[df['is_alive'] & df['la_is_postpartum'] & (df['pn_postnatal_period_in_weeks'] == week) & df['pn_emergency_event_mother'] & ~df['hs_is_inpatient']] @@ -744,9 +724,7 @@ def apply_risk_of_neonatal_complications_in_week_one(self, child_id, mother_id): if self.rng.random_sample() < risk_eons: df.at[child_id, 'pn_sepsis_early_neonatal'] = True self.sim.modules['NewbornOutcomes'].newborn_care_info[child_id]['sepsis_postnatal'] = True - - logger.info(key='newborn_complication', data={'newborn': child_id, - 'type': 'early_onset_sepsis'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['early_onset_sepsis'] += 1 def set_postnatal_complications_neonates(self, upper_and_lower_day_limits): """ @@ -771,9 +749,7 @@ def set_postnatal_complications_neonates(self, upper_and_lower_day_limits): for person in onset_sepsis.loc[onset_sepsis].index: if person in nci: nci[person]['sepsis_postnatal'] = True - - logger.info(key='newborn_complication', data={'newborn': person, - 'type': 'late_onset_sepsis'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['late_onset_sepsis'] += 1 # Then we determine if care will be sought for newly septic newborns care_seeking = pd.Series( @@ -830,6 +806,7 @@ def apply_risk_of_maternal_or_neonatal_death_postnatal(self, mother_or_child, in if potential_cause_of_death: mni[individual_id]['didnt_seek_care'] = True pregnancy_helper_functions.log_mni_for_maternal_death(self, individual_id) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['direct_mat_death'] += 1 self.sim.modules['Demography'].do_death(individual_id=individual_id, cause=potential_cause_of_death, originating_module=self.sim.modules['PostnatalSupervisor']) del mni[individual_id] @@ -862,7 +839,7 @@ def apply_risk_of_maternal_or_neonatal_death_postnatal(self, mother_or_child, in # If this neonate will die then we make the appropriate changes if self.rng.random_sample() < risk_of_death: - + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[f'{cause}_n_death'] += 1 self.sim.modules['Demography'].do_death(individual_id=individual_id, cause=cause, originating_module=self.sim.modules['PostnatalSupervisor']) @@ -886,6 +863,7 @@ def apply(self, population): df = population.props store_dalys_in_mni = pregnancy_helper_functions.store_dalys_in_mni mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info + mnh_counter = self.sim.modules['PregnancySupervisor'].mnh_outcome_counter # ================================ UPDATING LENGTH OF POSTPARTUM PERIOD IN WEEKS ============================ # Here we update how far into the postpartum period each woman who has recently delivered is @@ -941,7 +919,20 @@ def apply(self, population): # Set mni[person]['delete_mni'] to True meaning after the next DALY event each womans MNI dict is deleted for person in week_8_postnatal_women.loc[week_8_postnatal_women].index: + pn_checks = df.at[person, 'la_pn_checks_maternal'] + mni[person]['delete_mni'] = True + + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['six_week_survivors'] += 1 + + if df.at[person, 'pn_anaemia_following_pregnancy'] != 'none': + mnh_counter[f'pn_anaemia_{df.at[person, "pn_anaemia_following_pregnancy"]}'] += 1 + + if pn_checks >= 3: + mnh_counter['m_pnc3+'] += 1 + else: + mnh_counter[f'm_pnc{pn_checks}'] += 1 + logger.info(key='total_mat_pnc_visits', data={'mother': person, 'visits': df.at[person, 'la_pn_checks_maternal'], 'anaemia': df.at[person, 'pn_anaemia_following_pregnancy']}) @@ -961,6 +952,13 @@ def apply(self, population): self.sim.start_date) for person in week_5_postnatal_neonates.loc[week_5_postnatal_neonates].index: + pn_checks = df.at[person, 'nb_pnc_check'] + + if pn_checks >= 3: + mnh_counter['n_pnc3+'] += 1 + else: + mnh_counter[f'n_pnc{pn_checks}'] += 1 + self.sim.modules['NewbornOutcomes'].set_disability_status(person) logger.info(key='total_neo_pnc_visits', data={'child': person, 'visits': df.at[person, 'nb_pnc_check']}) @@ -1027,9 +1025,7 @@ def apply(self, individual_id): if endo_result or ut_result or ssti_result: df.at[individual_id, 'pn_sepsis_late_postpartum'] = True store_dalys_in_mni(individual_id, mni, 'sepsis_onset', self.sim.date) - - logger.info(key='maternal_complication', data={'person': individual_id, 'type': 'sepsis', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['sepsis_postnatal'] += 1 # Sepsis secondary to endometritis is stored within the mni as it is used as a predictor in a linear model if endo_result: @@ -1045,10 +1041,7 @@ def apply(self, individual_id): if risk_secondary_pph > self.module.rng.random_sample(): df.at[individual_id, 'pn_postpartum_haem_secondary'] = True store_dalys_in_mni(individual_id, mni, 'secondary_pph_onset', self.sim.date) - - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'secondary_postpartum_haemorrhage', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['secondary_postpartum_haemorrhage'] += 1 # ------------------------------------------------ NEW ONSET ANAEMIA ------------------------------------------ # And then risk of developing anaemia... @@ -1065,11 +1058,6 @@ def apply(self, individual_id): store_dalys_in_mni(individual_id, mni, f'{df.at[individual_id, "pn_anaemia_following_pregnancy"]}_' f'anaemia_pp_onset', self.sim.date) - logger.info(key='maternal_complication', - data={'person': individual_id, - 'type': f'{df.at[individual_id, "pn_anaemia_following_pregnancy"]}_anaemia', - 'timing': 'postnatal'}) - # -------------------------------------------- HYPERTENSION ----------------------------------------------- # For women who remain hypertensive after delivery we apply a probability that this will resolve in the # first week after birth @@ -1114,9 +1102,8 @@ def log_new_progressed_cases(disease): if not new_onset_disease.empty: for person in new_onset_disease.index: - logger.info(key='maternal_complication', data={'person': person, - 'type': disease, - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[disease] += 1 + if disease == 'severe_pre_eclamp': mni[person]['new_onset_spe'] = True @@ -1136,19 +1123,14 @@ def log_new_progressed_cases(disease): if risk_pe_after_pregnancy > self.module.rng.random_sample(): df.at[individual_id, 'pn_htn_disorders'] = 'mild_pre_eclamp' df.at[individual_id, 'ps_prev_pre_eclamp'] = True - - logger.info(key='maternal_complication', data={'person': individual_id, 'type': 'mild_pre_eclamp', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['mild_pre_eclamp'] += 1 else: risk_gh_after_pregnancy = self.module.pn_linear_models['gest_htn_pn'].predict(df.loc[[ individual_id]])[individual_id] if risk_gh_after_pregnancy > self.module.rng.random_sample(): df.at[individual_id, 'pn_htn_disorders'] = 'gest_htn' - - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'mild_gest_htn', - 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['mild_gest_htn'] += 1 # ====================================== POSTNATAL CHECK ================================================== # Import the HSI which represents postnatal care diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index 8f7faa0503..91094acc48 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -9,6 +9,58 @@ from tlo import logging +def generate_mnh_outcome_counter(): + outcome_list = [ # early/abortive outcomes + 'ectopic_unruptured', 'ectopic_ruptured','multiple_pregnancy', 'twin_birth', 'placenta_praevia', + 'spontaneous_abortion', 'induced_abortion', 'complicated_spontaneous_abortion', + 'complicated_induced_abortion', 'induced_abortion_injury', 'induced_abortion_sepsis', + 'induced_abortion_haemorrhage','induced_abortion_other_comp','spontaneous_abortion_sepsis', + 'spontaneous_abortion_haemorrhage', 'spontaneous_abortion_other_comp', + + # antenatal onset outcomes + 'an_anaemia_mild', 'an_anaemia_moderate', 'an_anaemia_severe', + 'gest_diab', 'mild_pre_eclamp', 'mild_gest_htn','severe_pre_eclamp', 'eclampsia','severe_gest_htn', + 'syphilis', 'PROM', 'clinical_chorioamnionitis', 'placental_abruption', + 'mild_mod_antepartum_haemorrhage','severe_antepartum_haemorrhage', 'antenatal_stillbirth', + + # intrapartum/postpartum onset outcomes + 'obstruction_cpd', 'obstruction_malpos_malpres', 'obstruction_other','obstructed_labour', + 'uterine_rupture','sepsis_intrapartum','sepsis_endometritis', 'sepsis_urinary_tract', + 'sepsis_skin_soft_tissue', 'sepsis_postnatal', 'intrapartum_stillbirth', 'early_preterm_labour', + 'late_preterm_labour', 'post_term_labour', 'pph_uterine_atony', 'pph_retained_placenta', + 'pph_other', 'primary_postpartum_haemorrhage', 'secondary_postpartum_haemorrhage', + 'vesicovaginal_fistula', 'rectovaginal_fistula', 'pn_anaemia_mild', 'pn_anaemia_moderate', + 'pn_anaemia_severe', + + # newborn outcomes + 'congenital_heart_anomaly', 'limb_or_musculoskeletal_anomaly', 'urogenital_anomaly', + 'digestive_anomaly', 'other_anomaly', 'mild_enceph', 'moderate_enceph', + 'severe_enceph', 'respiratory_distress_syndrome', 'not_breathing_at_birth', 'low_birth_weight', + 'macrosomia', 'small_for_gestational_age', 'early_onset_sepsis', 'late_onset_sepsis', + + # death outcomes + 'direct_mat_death', 'six_week_survivors','induced_abortion_m_death', 'spontaneous_abortion_m_death', + 'ectopic_pregnancy_m_death', 'severe_gestational_hypertension_m_death', + 'severe_pre_eclampsia_m_death', 'eclampsia_m_death', 'antepartum_haemorrhage_m_death', + 'antenatal_sepsis_m_death', + 'intrapartum_sepsis_m_death', 'postpartum_sepsis_m_death', 'uterine_rupture_m_death', + 'postpartum_haemorrhage_m_death','secondary_postpartum_haemorrhage_m_death', + 'early_onset_sepsis_n_death', 'late_onset_sepsis_n_death', 'encephalopathy_n_death', + 'neonatal_respiratory_depression_n_death', 'preterm_other_n_death', + 'respiratory_distress_syndrome_n_death', 'congenital_heart_anomaly_n_death', + 'limb_or_musculoskeletal_anomaly_n_death', 'urogenital_anomaly_n_death', 'digestive_anomaly_n_death', + 'other_anomaly_n_death', + + # service coverage outcomes + 'anc0', 'anc1', 'anc2', 'anc3', 'anc4', 'anc5', 'anc6', 'anc7', 'anc8', 'anc8+', + 'home_birth_delivery', 'hospital_delivery', 'health_centre_delivery', + 'm_pnc0', 'm_pnc1', 'm_pnc2', 'm_pnc3+', 'n_pnc0', 'n_pnc1', 'n_pnc2', 'n_pnc3+'] + + mnh_outcome_counter = {k: 0 for k in outcome_list} + + return {'counter': mnh_outcome_counter, + 'outcomes': outcome_list} + def get_list_of_items(self, item_list): """ Uses get_item_code_from_item_name to return item codes for a list of named items @@ -20,6 +72,95 @@ def get_list_of_items(self, item_list): return codes +def check_int_deliverable(self, int_name, hsi_event, + q_param=None, cons=None, opt_cons=None, equipment=None, dx_test=None): + """ + This function is called to determine if an intervention within the MNH modules can be delivered to an individual + during a given HSI. This applied to all MNH interventions. If analyses are being conducted in which the probability + of intervention delivery should be set explicitly, this is achieved during this function. Otherwise, probability of + intervention delivery is determined by any module-level quality parameters, consumable availability, and + (if applicable) the results of any dx_tests. Equipment is also declared. + + TODO: what about interventions outside mnh modules + +: param self: module + param int_name: items for code look up + param hsi_event: module + param q_param: items for code look up + param cons: module + param opt_cons: items for code look up + param equipment: module + param dx_test: items for code look up + """ + + df = self.sim.population.props + individual_id = hsi_event.target + p_params = self.sim.modules['PregnancySupervisor'].current_parameters + + # Firstly, we determine if an analysis is currently being conducted during which the probability of intervention + # delivery is being overridden + # To do: replace this parameter + if p_params['ps_analysis_in_progress'] and (int_name in p_params['interventions_under_analysis']): + + # If so, we determine if this intervention will be delivered given the set probability of delivery. + can_int_run_analysis = self.rng.random_sample() < p_params['intervention_analysis_availability'] + + # The intervention has no effect + if not can_int_run_analysis: + return False + + else: + # The intervention will have an effect. If this is an intervention which leads to an outcome dependent on + # correct identification of a condition through a dx_test we account for that here. + if dx_test is not None: + test = self.sim.modules['HealthSystem'].dx_manager.dx_tests[dx_test] + + if test[0].target_categories is None and (df.at[individual_id, test[0].property]): + return True + + elif ((test[0].target_categories is not None) and + (df.at[individual_id, test[0].property] in test[0].target_categories)): + return True + + else: + return False + + else: + return True + + else: + + # If analysis is not being conducted, intervention delivery is dependent on quality parameters, consumable + # availability and dx_test results + quality = False + consumables = False + test = False + + if ((q_param is None) or + all([self.rng.random_sample() < value for value in q_param])): + quality = True + + if equipment is not None: + hsi_event.add_equipment(equipment) + + if ((cons is None) or + (hsi_event.get_consumables(item_codes=cons if not None else [], + optional_item_codes=opt_cons if not None else []))): + consumables = True + + if cons is None and opt_cons is not None: + hsi_event.get_consumables(item_codes= [], optional_item_codes=opt_cons) + + if ((dx_test is None) or + (self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run=dx_test, hsi_event=hsi_event))): + test = True + + if quality and consumables and test: + return True + + else: + return False + def return_cons_avail(self, hsi_event, cons, opt_cons): """ @@ -283,7 +424,10 @@ def update_current_parameter_dictionary(self, list_position): for key, value in self.parameters.items(): if isinstance(value, list): - self.current_parameters[key] = self.parameters[key][list_position] + if not value or (len(value)) == 1: + self.current_parameters[key] = self.parameters[key] + else: + self.current_parameters[key] = self.parameters[key][list_position] else: if list_position == 0: self.current_parameters[key] = self.parameters[key] @@ -321,7 +465,7 @@ def log_mni_for_maternal_death(self, person_id): logger.info(key='death_mni', data=mni_to_log) -def calculate_risk_of_death_from_causes(self, risks): +def calculate_risk_of_death_from_causes(self, risks, target): """ This function calculates risk of death in the context of one or more 'death causing' complications in a mother of a newborn. In addition, it determines if the complication(s) will cause death or not. If death occurs the function @@ -349,7 +493,8 @@ def calculate_risk_of_death_from_causes(self, risks): # Now use the list of probabilities to conduct a weighted random draw to determine primary cause of death cause_of_death = self.rng.choice(list(risks.keys()), p=probs) - # Return the primary cause of death so that it can be passed to the demography function + # Return and log the primary cause of death so that it can be passed to the demography function + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[f'{cause_of_death}_{target}_death'] += 1 return cause_of_death else: # Return false if death will not occur @@ -460,7 +605,7 @@ def apply_effect_of_anaemia(cause): risks.update(risk) # Call return the result from calculate_risk_of_death_from_causes function - return calculate_risk_of_death_from_causes(self, risks) + return calculate_risk_of_death_from_causes(self, risks, target='m') # if she is not at risk of death as she has no complications we return false to the module return False @@ -530,7 +675,7 @@ def check_for_risk_of_death_from_cause_neonatal(self, individual_id): risks.update(risk) # Return the result from calculate_risk_of_death_from_causes function (returns primary cause of death or False) - return calculate_risk_of_death_from_causes(self, risks) + return calculate_risk_of_death_from_causes(self, risks, target='n') # if they is not at risk of death as they has no complications we return False to the module return False diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 7dd8819ab6..568e39c915 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -62,6 +62,10 @@ def __init__(self, name=None, resourcefilepath=None): # This variable will store a Bitset handler for the property ps_abortion_complications self.abortion_complications = None + # Finally we create a dictionary to capture the frequency of key outcomes for logging + mnh_oc = pregnancy_helper_functions.generate_mnh_outcome_counter() + self.mnh_outcome_counter = mnh_oc['counter'] + INIT_DEPENDENCIES = {'Demography'} OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden', 'Malaria', 'CardioMetabolicDisorders', 'Hiv'} @@ -385,6 +389,15 @@ def __init__(self, name=None, resourcefilepath=None): 'sens_analysis_max': Parameter( Types.BOOL, 'Signals within the analysis event and code that sensitivity analysis is being undertaken in ' 'which the maximum coverage of ANC is enforced'), + + 'intervention_availability': Parameter( + Types.DATA_FRAME, ''), + 'interventions_analysis': Parameter( + Types.BOOL, ''), + 'interventions_under_analysis': Parameter( + Types.LIST, ''), + 'intervention_analysis_availability': Parameter( + Types.REAL, ''), } PROPERTIES = { @@ -433,10 +446,14 @@ def __init__(self, name=None, resourcefilepath=None): } def read_parameters(self, data_folder): + p = self.parameters + # load parameters from the resource file - parameter_dataframe = pd.read_excel(Path(self.resourcefilepath) / 'ResourceFile_PregnancySupervisor.xlsx', - sheet_name='parameter_values') - self.load_parameters_from_dataframe(parameter_dataframe) + workbook = pd.read_excel(Path(self.resourcefilepath) / 'ResourceFile_PregnancySupervisor.xlsx', + sheet_name=None) + self.load_parameters_from_dataframe(workbook["parameter_values"]) + workbook['intervention_availability'].set_index('intervention', inplace=True) + p['intervention_availability'] = workbook['intervention_availability'] # Here we map 'disability' parameters to associated DALY weights to be passed to the health burden module. # Currently this module calculates and reports all DALY weights from all maternal modules @@ -523,13 +540,19 @@ def initialise_simulation(self, sim): sim.date + DateOffset(years=1)) # ...and register and schedule the parameter update event - sim.schedule_event(ParameterUpdateEvent(self), - Date(2015, 1, 1)) + if self.sim.date.year < 2015: + sim.schedule_event(ParameterUpdateEvent(self), + Date(2015, 1, 1)) + else: + sim.schedule_event(ParameterUpdateEvent(self), + Date(self.sim.date.year, 1, 1)) # ... and finally register and schedule the parameter override event. This is used in analysis scripts to change # key parameters after the simulation 'burn in' period. The event is schedule to run even when analysis is not # conducted but no changes to parameters can be made. - sim.schedule_event(PregnancyAnalysisEvent(self), Date(params['analysis_year'], 1, 1)) + + if self.sim.date.year <= params['analysis_year']: + sim.schedule_event(PregnancyAnalysisEvent(self), Date(params['analysis_year'], 1, 1)) # ==================================== LINEAR MODEL EQUATIONS ================================================= # Next we scale linear models according to distribution of predictors in the dataframe at baseline @@ -654,10 +677,8 @@ def further_on_birth_pregnancy_supervisor(self, mother_id): df.at[mother_id, 'ps_date_of_anc1'] = pd.NaT # And store her anaemia status to calculate the prevalence of anaemia on birth - logger.info(key='conditions_on_birth', data={'mother': mother_id, - 'anaemia_status': df.at[mother_id, 'ps_anaemia_in_pregnancy'], - 'gdm_status': df.at[mother_id, 'ps_gest_diab'], - 'htn_status': df.at[mother_id, 'ps_htn_disorders']}) + if df.at[mother_id, 'ps_anaemia_in_pregnancy'] != 'none': + self.mnh_outcome_counter[f'an_anaemia_{df.at[mother_id, "ps_anaemia_in_pregnancy"]}'] += 1 # We currently assume that hyperglycemia due to gestational diabetes resolves following birth if df.at[mother_id, 'ps_gest_diab'] != 'none': @@ -967,9 +988,7 @@ def do_after_abortion(self, individual_id, type_abortion): params = self.current_parameters # Log the pregnancy loss - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': f'{type_abortion}', - 'timing': 'antenatal'}) + self.mnh_outcome_counter[type_abortion] += 1 # This function officially ends a pregnancy through the contraception module (updates 'is_pregnant' and # determines post pregnancy contraception) @@ -996,9 +1015,7 @@ def do_after_abortion(self, individual_id, type_abortion): risk_of_complications = params['prob_complicated_ia'] if self.rng.random_sample() < risk_of_complications: - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': f'complicated_{type_abortion}', - 'timing': 'antenatal'}) + self.mnh_outcome_counter[f'complicated_{type_abortion}'] += 1 self.apply_risk_of_abortion_complications(individual_id, f'{type_abortion}') @@ -1016,31 +1033,23 @@ def apply_risk_of_abortion_complications(self, individual_id, cause): if cause == 'induced_abortion': if self.rng.random_sample() < params['prob_injury_post_abortion']: self.abortion_complications.set([individual_id], 'injury') - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': f'{cause}_injury', - 'timing': 'antenatal'}) + self.mnh_outcome_counter[f'{cause}_injury'] += 1 if self.rng.random_sample() < params['prob_haemorrhage_post_abortion']: self.abortion_complications.set([individual_id], 'haemorrhage') pregnancy_helper_functions.store_dalys_in_mni(individual_id, mni, 'abortion_haem_onset', self.sim.date) - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': f'{cause}_haemorrhage', - 'timing': 'antenatal'}) + self.mnh_outcome_counter[f'{cause}_haemorrhage'] += 1 if self.rng.random_sample() < params['prob_sepsis_post_abortion']: self.abortion_complications.set([individual_id], 'sepsis') pregnancy_helper_functions.store_dalys_in_mni(individual_id, mni, 'abortion_sep_onset', self.sim.date) - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': f'{cause}_sepsis', - 'timing': 'antenatal'}) + self.mnh_outcome_counter[f'{cause}_sepsis'] += 1 if not self.abortion_complications.has_any([individual_id], 'sepsis', 'haemorrhage', 'injury', first=True): self.abortion_complications.set([individual_id], 'other') - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': f'{cause}_other_comp', - 'timing': 'antenatal'}) + self.mnh_outcome_counter[f'{cause}_other_comp'] += 1 # We assume only women with complicated abortions will experience disability pregnancy_helper_functions.store_dalys_in_mni(individual_id, mni, 'abortion_onset', self.sim.date) @@ -1099,10 +1108,8 @@ def apply_risk_of_gestational_diabetes(self, gestation_of_interest): df.loc[gest_diab.loc[gest_diab].index, 'ps_gest_diab'] = 'uncontrolled' df.loc[gest_diab.loc[gest_diab].index, 'ps_prev_gest_diab'] = True - for person in gest_diab.loc[gest_diab].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'gest_diab', - 'timing': 'antenatal'}) + self.mnh_outcome_counter['gest_diab'] += len(gest_diab.loc[gest_diab].index) + def apply_risk_of_hypertensive_disorders(self, gestation_of_interest): """ @@ -1123,10 +1130,7 @@ def apply_risk_of_hypertensive_disorders(self, gestation_of_interest): df.loc[pre_eclampsia.loc[pre_eclampsia].index, 'ps_prev_pre_eclamp'] = True df.loc[pre_eclampsia.loc[pre_eclampsia].index, 'ps_htn_disorders'] = 'mild_pre_eclamp' - for person in pre_eclampsia.loc[pre_eclampsia].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'mild_pre_eclamp', - 'timing': 'antenatal'}) + self.mnh_outcome_counter['mild_pre_eclamp'] += len(pre_eclampsia.loc[pre_eclampsia].index) # -------------------------------- RISK OF GESTATIONAL HYPERTENSION -------------------------------------- # For women who dont develop pre-eclampsia during this month, we apply a risk of gestational hypertension @@ -1137,10 +1141,7 @@ def apply_risk_of_hypertensive_disorders(self, gestation_of_interest): df.loc[gest_hypertension.loc[gest_hypertension].index, 'ps_htn_disorders'] = 'gest_htn' - for person in gest_hypertension.loc[gest_hypertension].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'mild_gest_htn', - 'timing': 'antenatal'}) + self.mnh_outcome_counter['mild_gest_htn'] += len(gest_hypertension.loc[gest_hypertension].index) def apply_risk_of_progression_of_hypertension(self, gestation_of_interest): """ @@ -1195,9 +1196,8 @@ def log_new_progressed_cases(disease): # And log all of the new onset cases of any hypertensive disease for person in new_onset_disease.index: - logger.info(key='maternal_complication', data={'person': person, - 'type': disease, - 'timing': 'antenatal'}) + self.mnh_outcome_counter[disease] +=1 + if disease == 'severe_pre_eclamp': self.mother_and_newborn_info[person]['new_onset_spe'] = True @@ -1249,6 +1249,7 @@ def apply_risk_of_death_from_hypertension(self, gestation_of_interest): if not at_risk_of_death_htn.loc[at_risk_of_death_htn].empty: # Those women who die have InstantaneousDeath scheduled for person in at_risk_of_death_htn.loc[at_risk_of_death_htn].index: + self.mnh_outcome_counter['severe_gestational_hypertension_m_death'] += 1 self.sim.modules['Demography'].do_death(individual_id=person, cause='severe_gestational_hypertension', originating_module=self.sim.modules['PregnancySupervisor']) @@ -1269,10 +1270,7 @@ def apply_risk_of_placental_abruption(self, gestation_of_interest): ~df['la_currently_in_labour']]) df.loc[placenta_abruption.loc[placenta_abruption].index, 'ps_placental_abruption'] = True - for person in placenta_abruption.loc[placenta_abruption].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'placental_abruption', - 'timing': 'antenatal'}) + self.mnh_outcome_counter['placental_abruption'] += len(placenta_abruption.loc[placenta_abruption].index) def apply_risk_of_antepartum_haemorrhage(self, gestation_of_interest): """ @@ -1310,10 +1308,7 @@ def apply_risk_of_antepartum_haemorrhage(self, gestation_of_interest): severe_women.loc[severe_women].index.to_series().apply( pregnancy_helper_functions.store_dalys_in_mni, mni=mni, mni_variable='severe_aph_onset', date=self.sim.date) - for person in severe_women.loc[severe_women].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'severe_antepartum_haemorrhage', - 'timing': 'antenatal'}) + self.mnh_outcome_counter['severe_antepartum_haemorrhage'] += len(severe_women.loc[severe_women].index) non_severe_women = (df.loc[antepartum_haemorrhage.loc[antepartum_haemorrhage].index, 'ps_antepartum_haemorrhage'] != 'severe') @@ -1322,10 +1317,7 @@ def apply_risk_of_antepartum_haemorrhage(self, gestation_of_interest): pregnancy_helper_functions.store_dalys_in_mni, mni=mni, mni_variable='mild_mod_aph_onset', date=self.sim.date) - for person in non_severe_women.loc[non_severe_women].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'mild_mod_antepartum_haemorrhage', - 'timing': 'antenatal'}) + self.mnh_outcome_counter['mild_mod_antepartum_haemorrhage'] += len(non_severe_women.loc[non_severe_women].index) def apply_risk_of_premature_rupture_of_membranes_and_chorioamnionitis(self, gestation_of_interest): """ @@ -1349,11 +1341,7 @@ def apply_risk_of_premature_rupture_of_membranes_and_chorioamnionitis(self, gest # We allow women to seek care for PROM df.loc[prom.loc[prom].index, 'ps_emergency_event'] = True - - for person in prom.loc[prom].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'PROM', - 'timing': 'antenatal'}) + self.mnh_outcome_counter['PROM'] += len(prom.loc[prom].index) # Determine if those with PROM will develop infection prior to care seeking infection = pd.Series(self.rng.random_sample(len(prom.loc[prom])) < params['prob_chorioamnionitis'], @@ -1366,9 +1354,7 @@ def apply_risk_of_premature_rupture_of_membranes_and_chorioamnionitis(self, gest for person in infection.loc[infection].index: self.mother_and_newborn_info[person]['chorio_in_preg'] = True - logger.info(key='maternal_complication', data={'person': person, - 'type': 'clinical_chorioamnionitis', - 'timing': 'antenatal'}) + self.mnh_outcome_counter['clinical_chorioamnionitis'] += 1 def apply_risk_of_preterm_labour(self, gestation_of_interest): """ @@ -1432,7 +1418,7 @@ def update_variables_post_still_birth_for_data_frame(self, women): for person in women.index: self.sim.modules['Contraception'].end_pregnancy(person) mni[person]['delete_mni'] = True - logger.info(key='antenatal_stillbirth', data={'mother': person}) + self.mnh_outcome_counter['antenatal_stillbirth'] += 1 # Call functions across the modules to ensure properties are rest self.sim.modules['Labour'].reset_due_date(id_or_index=women.index, new_due_date=pd.NaT) @@ -1454,7 +1440,7 @@ def update_variables_post_still_birth_for_individual(self, individual_id): df.at[individual_id, 'ps_prev_stillbirth'] = True mni[individual_id]['delete_mni'] = True - logger.info(key='antenatal_stillbirth', data={'mother': individual_id}) + self.mnh_outcome_counter['antenatal_stillbirth'] += 1 # Reset pregnancy and schedule possible update of contraception self.sim.modules['Contraception'].end_pregnancy(individual_id) @@ -1584,6 +1570,7 @@ def apply_risk_of_death_from_monthly_complications(self, individual_id): pregnancy_helper_functions.log_mni_for_maternal_death(self, individual_id) self.sim.modules['Demography'].do_death(individual_id=individual_id, cause=potential_cause_of_death, originating_module=self.sim.modules['PregnancySupervisor']) + self.mnh_outcome_counter['direct_mat_death'] += 1 del mni[individual_id] # If not we reset variables and the woman survives @@ -1765,9 +1752,7 @@ def apply(self, population): # For women whose pregnancy is ectopic we scheduled them to the EctopicPregnancyEvent in between 3-5 weeks # (this simulates time period prior to which symptoms onset- and may trigger care seeking) for person in ectopic_risk.loc[ectopic_risk].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'ectopic_unruptured', - 'timing': 'antenatal'}) + self.module.mnh_outcome_counter['ectopic_unruptured'] += 1 self.sim.schedule_event(EctopicPregnancyEvent(self.module, person), (self.sim.date + pd.Timedelta(days=7 * 3 + self.module.rng.randint(0, 7 * 2)))) @@ -1782,11 +1767,7 @@ def apply(self, population): < params['prob_multiples'], index=multiple_risk.loc[multiple_risk].index) df.loc[multiples.loc[multiples].index, 'ps_multiple_pregnancy'] = True - - for person in multiples.loc[multiples].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'multiple_pregnancy', - 'timing': 'antenatal'}) + self.module.mnh_outcome_counter['multiple_pregnancy'] += len(multiples.loc[multiples].index) # -----------------------------APPLYING RISK OF PLACENTA PRAEVIA ------------------------------------------- # Next,we apply a one off risk of placenta praevia (placenta will grow to cover the cervix either partially or @@ -1796,11 +1777,7 @@ def apply(self, population): df.loc[new_pregnancy & (df['ps_ectopic_pregnancy'] == 'none')]) df.loc[placenta_praevia.loc[placenta_praevia].index, 'ps_placenta_praevia'] = True - - for person in placenta_praevia.loc[placenta_praevia].index: - logger.info(key='maternal_complication', data={'person': person, - 'type': 'placenta_praevia', - 'timing': 'antenatal'}) + self.module.mnh_outcome_counter['placenta_praevia'] += len(placenta_praevia.loc[placenta_praevia].index) # ------------------------- APPLYING RISK OF SYPHILIS INFECTION DURING PREGNANCY --------------------------- # Finally apply risk that syphilis will develop during pregnancy @@ -1984,9 +1961,7 @@ def apply(self, individual_id): if not df.at[individual_id, 'is_alive'] or (df.at[individual_id, 'ps_ectopic_pregnancy'] != 'not_ruptured'): return - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'ectopic_ruptured', - 'timing': 'antenatal'}) + self.module.mnh_outcome_counter['ectopic_ruptured'] += 1 # Set the variable df.at[individual_id, 'ps_ectopic_pregnancy'] = 'ruptured' @@ -2028,6 +2003,7 @@ def apply(self, individual_id): pregnancy_helper_functions.log_mni_for_maternal_death(self.module, individual_id) mni[individual_id]['delete_mni'] = True + self.module.mnh_outcome_counter[f'{self.cause}_m_death'] += 1 self.sim.modules['Demography'].do_death(individual_id=individual_id, cause=f'{self.cause}', originating_module=self.sim.modules['PregnancySupervisor']) @@ -2094,10 +2070,8 @@ def apply(self, individual_id): return df.at[individual_id, 'ps_syphilis'] = True - logger.info(key='maternal_complication', data={'person': individual_id, - 'type': 'syphilis', - 'timing': 'antenatal'}) + self.module.mnh_outcome_counter['syphilis'] += 1 class ParameterUpdateEvent(Event, PopulationScopeEventMixin): """This is ParameterUpdateEvent. It is scheduled to occur once on 2015 to update parameters being used by the @@ -2163,7 +2137,8 @@ def apply(self, population): params['alternative_anc_quality'] or \ params['alternative_ip_anc_quality'] or \ params['sens_analysis_max'] or \ - params['sens_analysis_min']: + params['sens_analysis_min'] or \ + params['interventions_analysis']: # Update this parameter which is a signal used in the pregnancy_helper_function_file to ensure that # alternative functionality for determining availability of interventions only occurs when analysis is @@ -2235,37 +2210,120 @@ def __init__(self, module): def apply(self, population): df = self.sim.population.props + c = self.module.mnh_outcome_counter + + # DENOMINATORS + # Define denominators used to calculate rates, cancel the event if any are 0 to prevent division by 0 errors + live_births = len(df[(df['date_of_birth'].dt.year == self.sim.date.year - 1) & (df['mother_id'] >= 0)]) + pregnancies =len(df[df['date_of_last_pregnancy'].dt.year == self.sim.date.year - 1]) + comp_pregnancies = (c['ectopic_unruptured'] + c['spontaneous_abortion'] + + c['induced_abortion'] + c['antenatal_stillbirth'] + + c['intrapartum_stillbirth'] + live_births) + deliveries = live_births - c['twin_birth'] + total_births = live_births + c['antenatal_stillbirth'] + c['intrapartum_stillbirth'] + total_preterm_birth = c['early_preterm_labour'] + c['late_preterm_labour'] + + for denom in [live_births, pregnancies, comp_pregnancies, deliveries, total_births, total_preterm_birth]: + if denom == 0: + return - women_reproductive_age = len(df.index[(df.is_alive & (df.sex == 'F') & (df.age_years > 14) & - (df.age_years < 50))]) - pregnant_at_year_end = len(df.index[df.is_alive & df.is_pregnant]) - women_with_previous_sa = len(df.index[(df.is_alive & (df.sex == 'F') & (df.age_years > 14) & - (df.age_years < 50) & df.ps_prev_spont_abortion)]) - women_with_previous_pe = len(df.index[(df.is_alive & (df.sex == 'F') & (df.age_years > 14) & - (df.age_years < 50) & df.ps_prev_pre_eclamp)]) - women_with_hysterectomy = len(df.index[(df.is_alive & (df.sex == 'F') & (df.age_years > 14) & - (df.age_years < 50) & df.la_has_had_hysterectomy)]) - - yearly_prev_sa = (women_with_previous_sa / women_reproductive_age) * 100 - yearly_prev_pe = (women_with_previous_pe / women_reproductive_age) * 100 - yearly_prev_hysterectomy = (women_with_hysterectomy / women_reproductive_age) * 100 - - parity_list = list() - for parity in [0, 1, 2, 3, 4, 5]: - if parity < 5: - par = len(df.index[(df.is_alive & (df.sex == 'F') & (df.age_years > 14) & (df.age_years < 50) & - (df.la_parity == parity))]) - else: - par = len(df.index[(df.is_alive & (df.sex == 'F') & (df.age_years > 14) & (df.age_years < 50) & - (df.la_parity >= parity))]) - - yearly_prev = (par / women_reproductive_age) * 100 - parity_list.append(yearly_prev) - - logger.info(key='preg_info', - data={'women_repro_age': women_reproductive_age, - 'women_pregnant': pregnant_at_year_end, - 'prev_sa': yearly_prev_sa, - 'prev_pe': yearly_prev_pe, - 'hysterectomy': yearly_prev_hysterectomy, - 'parity': parity_list}) + # MATERNAL COMPLICATION INCIDENCE + # Log the yearly dictionary (allows for analyses with outcomes not used in this event) + logger.info(key='yearly_mnh_counter_dict', data=c) + + # Calculate and store rates of key maternal and neonatal complications + def rate (count, denom, multiplier): + return (count/denom) * multiplier + + total_an_anaemia_cases = c['an_anaemia_mild'] + c['an_anaemia_moderate'] + c['an_anaemia_severe'] + total_pn_anaemia_cases = c['pn_anaemia_mild'] + c['pn_anaemia_moderate'] + c['pn_anaemia_severe'] + total_aph = c['mild_mod_antepartum_haemorrhage'] + c['severe_antepartum_haemorrhage'] + total_sepsis = c['clinical_chorioamnionitis'] + c['sepsis_intrapartum'] + c['sepsis_postnatal'] + total_pph = c['primary_postpartum_haemorrhage'] + c['secondary_postpartum_haemorrhage'] + total_fistula = c['vesicovaginal_fistula'] + c['rectovaginal_fistula'] + total_neo_sepsis = c['early_onset_sepsis'] + c['late_onset_sepsis'] + total_neo_enceph = c['mild_enceph'] + c['moderate_enceph'] + c['severe_enceph'] + total_neo_resp_conds = c['respiratory_distress_syndrome'] + c['not_breathing_at_birth'] + total_neo_enceph + total_cba = (c['congenital_heart_anomaly'] + c['limb_or_musculoskeletal_anomaly'] + + c['urogenital_anomaly'] + c['digestive_anomaly'] + c['other_anomaly']) + + logger.info(key='mat_comp_incidence', + data={'an_anaemia': rate(total_an_anaemia_cases, live_births, 100), + 'ectopic_unruptured' : rate(c['ectopic_unruptured'], pregnancies, 1000), + 'induced_abortion' : rate(c['induced_abortion'], comp_pregnancies, 1000), + 'spontaneous_abortion': rate(c['spontaneous_abortion'], comp_pregnancies, 1000), + 'placenta_praevia': rate(c['placenta_praevia'], pregnancies, 1000), + 'gest_diab': rate(c['gest_diab'], comp_pregnancies, 1000), + 'PROM': rate(c['PROM'], comp_pregnancies, 1000), + 'preterm_birth': rate(total_preterm_birth, live_births, 100), + 'antepartum_haem': rate(total_aph, live_births, 1000), + 'obstructed_labour': rate(c['obstructed_labour'], live_births, 1000), + 'uterine_rupture': rate(c['uterine_rupture'], live_births, 1000), + 'sepsis': rate(total_sepsis, live_births, 1000), + 'mild_pre_eclamp': rate(c['mild_pre_eclamp'], live_births, 1000), + 'mild_gest_htn': rate(c['mild_gest_htn'], live_births, 1000), + 'severe_pre_eclamp': rate(c['severe_pre_eclamp'], live_births, 1000), + 'severe_gest_htn': rate(c['severe_gest_htn'], live_births, 1000), + 'eclampsia': rate(c['eclampsia'], live_births, 1000), + 'postpartum_haem': rate(total_pph, live_births, 1000), + 'fistula': rate(total_fistula, live_births, 1000), + 'pn_anaemia': rate(total_pn_anaemia_cases, c['six_week_survivors'], 100)}) + + # NEWBORN COMPLICATIONS + logger.info(key='nb_comp_incidence', + data={'twin_birth': rate(c['twin_birth'], deliveries, 100), + 'nb_sepsis': rate(total_neo_sepsis, live_births, 1000), + 'nb_enceph': rate(total_neo_enceph, live_births, 1000), + 'nb_resp_diff': rate(total_neo_resp_conds, live_births, 100), + 'nb_cba': rate(total_cba, live_births, 1000), + 'nb_rds': rate(c['respiratory_distress_syndrome'], total_preterm_birth, 1000), + 'nb_lbw': rate(c['low_birth_weight'], live_births, 100), + 'nb_macrosomia': rate(c['macrosomia'], live_births, 100), + 'nb_sga': rate(c['small_for_gestational_age'], live_births, 100)}) + + # DIRECT MATERNAL DEATHS, NEWBORN DEATHS AND STILLBIRTH + # Calculate and store rates of maternal and newborn death and stillbirth + neonatal_deaths = len(df[(df['date_of_death'].dt.year == self.sim.date.year - 1) & (df['age_days'] <= 28)]) + stillbirths = c['antenatal_stillbirth'] + c['intrapartum_stillbirth'] + + general_death_data = {'antenatal_sbr': rate(c['antenatal_stillbirth'], total_births, 1000), + 'intrapartum_sbr': rate(c['intrapartum_stillbirth'], total_births, 1000), + 'total_stillbirths': stillbirths, + 'sbr': rate(stillbirths, total_births, 1000), + 'neonatal_deaths': neonatal_deaths, + 'nmr' : rate(neonatal_deaths, live_births, 1000), + 'direct_maternal_deaths': c['direct_mat_death'], + 'direct_mmr': rate(c['direct_mat_death'], live_births, 100_000)} + + cause_specific_mmrs = {k: rate(c[k], total_births, 100_000) for k in c if 'm_death' in k} + cause_specific_nmrs = {k: rate(c[k], total_births, 1000) for k in c if 'n_death' in k} + general_death_data.update({**cause_specific_mmrs, **cause_specific_nmrs}) + + logger.info(key='deaths_and_stillbirths', data=general_death_data) + + # Finally log coverage of key health services + anc1 = sum(c[f'anc{i}'] for i in range(1, 9)) + c['anc8+'] + anc4 = sum(c[f'anc{i}'] for i in range(4, 9))+ c['anc8+'] + anc8 = c['anc8'] + c['anc8+'] + + m_pnc1 = sum(c[f'm_pnc{i}'] for i in range(1, 3)) + c['m_pnc3+'] + n_pnc1 = sum(c[f'm_pnc{i}'] for i in range(1, 3)) + c['m_pnc3+'] + + # HEALTH SERVICE COVERAGE + logger.info(key='service_coverage', + data={'anc1+': rate(anc1 , total_births, 100), + 'anc4+': rate(anc4, total_births, 100), + 'anc8+': rate(anc8, total_births, 100), + + 'fd_rate': rate(anc1 , total_births, 100), + 'hb_rate': rate(c['home_birth_delivery'] , total_births, 100), + 'hc_rate': rate(c['health_centre_delivery'] , total_births, 100), + 'hp_rate': rate(c['hospital_delivery'] , total_births, 100), + + 'm_pnc1+': rate(m_pnc1, total_births, 100), + 'n_pnc1+': rate(n_pnc1, total_births, 100)}) + + # Reset the dictionary so all values = 0 + mnh_oc = pregnancy_helper_functions.generate_mnh_outcome_counter() + outcome_list = mnh_oc['outcomes'] + self.module.mnh_outcome_counter = {k:0 for k in outcome_list} diff --git a/src/tlo/methods/schisto.py b/src/tlo/methods/schisto.py index 0e9735286a..f2a4253fad 100644 --- a/src/tlo/methods/schisto.py +++ b/src/tlo/methods/schisto.py @@ -170,7 +170,8 @@ def initialise_simulation(self, sim): sim.schedule_event(SchistoLoggingEvent(self), sim.date) # Schedule MDA events - if self.mda_execute: + # TODO: check with Tara shes happy with this (could come in as its own PR) + if self.mda_execute and self.sim.date.year == 2010: self._schedule_mda_events() def on_birth(self, mother_id, child_id): diff --git a/src/tlo/util.py b/src/tlo/util.py index 168b1d41a1..7d1f8c6d0e 100644 --- a/src/tlo/util.py +++ b/src/tlo/util.py @@ -113,10 +113,13 @@ def sample_outcome(probs: pd.DataFrame, rng: np.random.RandomState): cumsum = _probs.cumsum(axis=1) draws = pd.Series(rng.rand(len(cumsum)), index=cumsum.index) y = cumsum.gt(draws, axis=0) - outcome = y.idxmax(axis=1) + if not y.empty: + outcome = y.idxmax(axis=1) + # return as a dict of form {person_id: outcome} only in those cases where the outcome is one of the events. + return outcome.loc[outcome != '_'].to_dict() - # return as a dict of form {person_id: outcome} only in those cases where the outcome is one of the events. - return outcome.loc[outcome != '_'].to_dict() + else: + return dict() BitsetDType = Property.PANDAS_TYPE_MAP[Types.BITSET] diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py new file mode 100644 index 0000000000..3c6a46d43d --- /dev/null +++ b/tests/test_mnh_cohort.py @@ -0,0 +1,92 @@ +import os + +import pandas as pd + +from pathlib import Path + +from tlo import Date, Simulation, logging +from tlo.methods import mnh_cohort_module +from tlo.methods.fullmodel import fullmodel +from tlo.analysis.utils import parse_log_file + +# The resource files +try: + resourcefilepath = Path(os.path.dirname(__file__)) / '../resources' +except NameError: + # running interactively + resourcefilepath = Path('./resources') + +start_date = Date(2024, 1, 1) + + +def register_modules(sim): + """Defines sim variable and registers all modules that can be called when running the full suite of pregnancy + modules""" + + sim.register(*fullmodel(resourcefilepath=resourcefilepath), + mnh_cohort_module.MaternalNewbornHealthCohort(resourcefilepath=resourcefilepath)) + +def test_run_sim_with_mnh_cohort(tmpdir, seed): + sim = Simulation(start_date=start_date, seed=seed, log_config={"filename": "log", "custom_levels":{ + "*": logging.DEBUG},"directory": tmpdir}) + + register_modules(sim) + sim.make_initial_population(n=2500) + sim.simulate(end_date=Date(2025, 1, 2)) + + output= parse_log_file(sim.log_filepath) + live_births = len(output['tlo.methods.demography']['on_birth']) + + deaths_df = output['tlo.methods.demography']['death'] + prop_deaths_df = output['tlo.methods.demography.detail']['properties_of_deceased_persons'] + + dir_mat_deaths = deaths_df.loc[(deaths_df['label'] == 'Maternal Disorders')] + init_indir_mat_deaths = prop_deaths_df.loc[(prop_deaths_df['is_pregnant'] | prop_deaths_df['la_is_postpartum']) & + (prop_deaths_df['cause_of_death'].str.contains('Malaria|Suicide|ever_stroke|diabetes|' + 'chronic_ischemic_hd|ever_heart_attack|' + 'chronic_kidney_disease') | + (prop_deaths_df['cause_of_death'] == 'TB'))] + + hiv_mat_deaths = prop_deaths_df.loc[(prop_deaths_df['is_pregnant'] | prop_deaths_df['la_is_postpartum']) & + (prop_deaths_df['cause_of_death'].str.contains('AIDS_non_TB|AIDS_TB'))] + + indir_mat_deaths = len(init_indir_mat_deaths) + (len(hiv_mat_deaths) * 0.3) + total_deaths = len(dir_mat_deaths) + indir_mat_deaths + + # TOTAL_DEATHS + mmr = (total_deaths / live_births) * 100_000 + + print(f'The MMR for this simulation is {mmr}') + print(f'The maternal deaths for this simulation (unscaled) are {total_deaths}') + print(f'The total maternal deaths for this simulation (scaled) are ' + f'{total_deaths * output["tlo.methods.population"]["scaling_factor"]["scaling_factor"].values[0]}') + + maternal_dalys = output['tlo.methods.healthburden']['dalys_stacked']['Maternal Disorders'].sum() + print(f'The maternal DALYs for this simulation (unscaled) are {maternal_dalys}') + + +def test_mnh_cohort_module_updates_properties_as_expected(tmpdir, seed): + sim = Simulation(start_date=start_date, seed=seed, log_config={"filename": "log", "directory": tmpdir}) + + register_modules(sim) + sim.make_initial_population(n=1000) + sim.simulate(end_date=sim.date + pd.DateOffset(days=0)) + + df = sim.population.props + pop = df.loc[df.is_alive] + + assert (df.loc[pop.index, 'sex'] == 'F').all() + assert df.loc[pop.index, 'is_pregnant'].all() + assert not (pd.isnull(df.loc[pop.index, 'la_due_date_current_pregnancy'])).all() + assert (df.loc[pop.index, 'co_contraception'] == 'not_using').all() + + # orig = sim.population.new_row + # assert (df.dtypes == orig.dtypes).all() + +# def test_mnh_cohort_module_updates_properties_as_expected(tmpdir, seed): +# sim = Simulation(start_date=start_date, seed=seed, log_config={"filename": "log", "directory": tmpdir}) +# +# register_modules(sim) +# sim.make_initial_population(n=1000) +# sim.simulate(end_date=sim.date + pd.DateOffset(days=0)) +# # to do: check properties!! diff --git a/tests/test_pregnancy_supervisor.py b/tests/test_pregnancy_supervisor.py index 31adcf03b5..4a1aafe0b7 100644 --- a/tests/test_pregnancy_supervisor.py +++ b/tests/test_pregnancy_supervisor.py @@ -123,8 +123,11 @@ def test_run_core_modules_normal_allocation_of_pregnancy(seed, tmpdir): # check that no errors have been logged during the simulation run output = parse_log_file(sim.log_filepath) - assert 'error' not in output['tlo.methods.pregnancy_supervisor'] - assert 'error' not in output['tlo.methods.care_of_women_during_pregnancy'] + if 'tlo.methods.pregnancy_supervisor' in output: + assert 'error' not in output['tlo.methods.pregnancy_supervisor'] + + if 'tlo.methods.care_of_women_during_pregnancy' in output: + assert 'error' not in output['tlo.methods.care_of_women_during_pregnancy'] @pytest.mark.slow @@ -136,13 +139,16 @@ def test_run_core_modules_high_volumes_of_pregnancy(seed, tmpdir): register_modules(sim) sim.make_initial_population(n=5000) set_all_women_as_pregnant_and_reset_baseline_parity(sim) - sim.simulate(end_date=Date(2011, 1, 1)) + sim.simulate(end_date=Date(2011, 1, 2)) check_dtypes(sim) # check that no errors have been logged during the simulation run output = parse_log_file(sim.log_filepath) - assert 'error' not in output['tlo.methods.pregnancy_supervisor'] - assert 'error' not in output['tlo.methods.care_of_women_during_pregnancy'] + if 'tlo.methods.pregnancy_supervisor' in output: + assert 'error' not in output['tlo.methods.pregnancy_supervisor'] + + if 'tlo.methods.care_of_women_during_pregnancy' in output: + assert 'error' not in output['tlo.methods.care_of_women_during_pregnancy'] @pytest.mark.slow @@ -172,14 +178,15 @@ def test_run_core_modules_high_volumes_of_pregnancy_hsis_cant_run(seed, tmpdir): sim.make_initial_population(n=5000) set_all_women_as_pregnant_and_reset_baseline_parity(sim) - sim.simulate(end_date=Date(2011, 1, 1)) + sim.simulate(end_date=Date(2011, 1, 2)) check_dtypes(sim) # check that no errors have been logged during the simulation run output = parse_log_file(sim.log_filepath) for module in ['pregnancy_supervisor', 'care_of_women_during_pregnancy', 'labour', 'postnatal_supervisor', 'newborn_outcomes']: - assert 'error' not in output[f'tlo.methods.{module}'] + if module in output: + assert 'error' not in output[f'tlo.methods.{module}'] @pytest.mark.slow @@ -220,13 +227,16 @@ def test_run_with_all_referenced_modules_registered(seed, tmpdir): sim.make_initial_population(n=5000) set_all_women_as_pregnant_and_reset_baseline_parity(sim) # keep high volume of pregnancy to increase risk of error - sim.simulate(end_date=Date(2011, 1, 1)) + sim.simulate(end_date=Date(2011, 1, 2)) check_dtypes(sim) # check that no errors have been logged during the simulation run output = parse_log_file(sim.log_filepath) - assert 'error' not in output['tlo.methods.pregnancy_supervisor'] - assert 'error' not in output['tlo.methods.care_of_women_during_pregnancy'] + if 'tlo.methods.pregnancy_supervisor' in output: + assert 'error' not in output['tlo.methods.pregnancy_supervisor'] + + if 'tlo.methods.care_of_women_during_pregnancy' in output: + assert 'error' not in output['tlo.methods.care_of_women_during_pregnancy'] def test_store_dalys_in_mni_function_and_daly_calculations(seed): @@ -1082,6 +1092,9 @@ def test_pregnancy_supervisor_gdm(seed): pregnancy_helper_functions.update_mni_dictionary(sim.modules['PregnancySupervisor'], woman) pregnancy_helper_functions.update_mni_dictionary(sim.modules['Labour'], woman) + sim.modules['PregnancySupervisor'].mother_and_newborn_info[woman]['delivery_setting'] = 'home_birth' + + # Run pregnancy supervisor event pregnancy_sup = pregnancy_supervisor.PregnancySupervisorEvent(module=sim.modules['PregnancySupervisor']) sim.date = sim.date + pd.DateOffset(weeks=20)