From 6a29cee74dde35c140e0a71e3e8425725389724a Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 19 Apr 2024 13:27:11 +0100 Subject: [PATCH 01/38] update contraception.py logging to capture df for new pregnancies --- .../full_model_long_run_cohort.py | 35 +++++++++++++++++++ src/tlo/methods/contraception.py | 6 ++++ 2 files changed, 41 insertions(+) create mode 100644 src/scripts/maternal_perinatal_analyses/scenario_files/full_model_long_run_cohort.py 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/contraception.py b/src/tlo/methods/contraception.py index 67d6684fce..ad61e1aeb5 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1068,6 +1068,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): From e23faaf5c531a89eeeced1d20115e33e2308bb8d Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 31 May 2024 11:32:16 +0100 Subject: [PATCH 02/38] create cohort module and cohort resource file. updated demography logging due to error when no males in population --- resources/ResourceFile_PregnancyCohort.xlsx | 3 + src/tlo/methods/demography.py | 4 +- src/tlo/methods/mnh_cohort_module.py | 257 ++++++++++++++++++++ tests/test_mnh_cohort.py | 54 ++++ 4 files changed, 316 insertions(+), 2 deletions(-) create mode 100644 resources/ResourceFile_PregnancyCohort.xlsx create mode 100644 src/tlo/methods/mnh_cohort_module.py create mode 100644 tests/test_mnh_cohort.py 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/src/tlo/methods/demography.py b/src/tlo/methods/demography.py index f3d3e3a8c4..3e99dd7653 100644 --- a/src/tlo/methods/demography.py +++ b/src/tlo/methods/demography.py @@ -781,8 +781,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/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py new file mode 100644 index 0000000000..da8c2e8598 --- /dev/null +++ b/src/tlo/methods/mnh_cohort_module.py @@ -0,0 +1,257 @@ +from pathlib import Path + +import numpy as np +import pandas as pd +from tlo import DateOffset, Module, Parameter, Property, Types, logging +from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent +from tlo.methods import Metadata +from tlo.methods.causes import Cause +from tlo.methods.hsi_event import HSI_Event + + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + +# --------------------------------------------------------------------------------------------------------- +# MODULE DEFINITIONS +# --------------------------------------------------------------------------------------------------------- + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +class MaternalNewbornHealthCohort(Module): + """ + + """ + + # INIT_DEPENDENCIES = {'Demography'} + # + # OPTIONAL_INIT_DEPENDENCIES = {''} + # + # ADDITIONAL_DEPENDENCIES = {''} + + # Declare Metadata (this is for a typical 'Disease Module') + 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): + # NB. Parameters passed to the module can be inserted in the __init__ definition. + + super().__init__(name) + self.resourcefilepath = resourcefilepath + + def read_parameters(self, data_folder): + """Read parameter values from file, if required. + To access files use: Path(self.resourcefilepath) / file_name + """ + 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 + """ + pass + + 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. + + """ + # cohort_prop_df = pd.read_excel(Path(self.resourcefilepath) / 'ResourceFile_PregnancyCohort.xlsx') + # from tlo.analysis.utils import parse_log_file + # cohort_prop_df = parse_log_file( + # '/Users/j_collins/PycharmProjects/TLOmodel/outputs/sejjj49@ucl.ac.uk/fullmodel_200k_cohort-2024-04-24T072206Z/0/0/fullmodel_200k_cohort__2024-04-24T072516.log', + # level=logging.DEBUG)['tlo.methods.contraception'] + # + # cohort_prop_df_for_pop = cohort_prop_df.loc[0:(len(self.sim.population.props))-1] + # self.sim.population.props = cohort_prop_df_for_pop + # + # # todo: pd.NaT values are not carried over when excel file was created. + # # df = self.sim.population.props + # # population = df.loc[df.is_alive] + # # for column in df.columns: + # # df.loc[population.index, column] = cohort_prop_df_for_pop[column] + # + # df = self.sim.population.props + # population = df.loc[df.is_alive] + # df.loc[population.index, 'date_of_last_pregnancy'] = sim.start_date + # df.loc[population.index, 'co_contraception'] = "not_using" + + + def on_birth(self, mother_id, child_id): + """Initialise our properties for a newborn individual. + + This is called by the simulation whenever a new person is born. + + :param mother_id: the mother for this child + :param child_id: the new child + """ + raise NotImplementedError + + def report_daly_values(self): + """ + This must send back a pd.Series or pd.DataFrame that reports on the average daly-weights that have been + experienced by persons in the previous month. Only rows for alive-persons must be returned. + If multiple causes in CAUSES_OF_DISABILITY are defined, a pd.DataFrame must be returned with a column + corresponding to each cause (but if only one cause in CAUSES_OF_DISABILITY is defined, the pd.Series does not + need to be given a specific name). + + To return a value of 0.0 (fully health) for everyone, use: + df = self.sim.population.props + return pd.Series(index=df.index[df.is_alive],data=0.0) + """ + raise NotImplementedError + + def on_hsi_alert(self, person_id, treatment_id): + """ + This is called whenever there is an HSI event commissioned by one of the other disease modules. + """ + + raise NotImplementedError + + +# --------------------------------------------------------------------------------------------------------- +# DISEASE MODULE EVENTS +# +# These are the events which drive the simulation of the disease. It may be a regular event that updates +# the status of all the population of subsections of it at one time. There may also be a set of events +# that represent disease events for particular persons. +# --------------------------------------------------------------------------------------------------------- + +class Skeleton_Event(RegularEvent, PopulationScopeEventMixin): + """A skeleton class for an event + + Regular events automatically reschedule themselves at a fixed frequency, + and thus implement discrete timestep type behaviour. The frequency is + specified when calling the base class constructor in our __init__ method. + """ + + def __init__(self, module): + """One line summary here + + We need to pass the frequency at which we want to occur to the base class + constructor using super(). We also pass the module that created this event, + so that random number generators can be scoped per-module. + + :param module: the module that created this event + """ + super().__init__(module, frequency=DateOffset(months=1)) + assert isinstance(module, MaternalNewbornHealthCohort) + + def apply(self, population): + """Apply this event to the population. + + :param population: the current population + """ + raise NotImplementedError + + +# --------------------------------------------------------------------------------------------------------- +# LOGGING EVENTS +# +# Put the logging events here. There should be a regular logger outputting current states of the +# population. There may also be a logging event that is driven by particular events. +# --------------------------------------------------------------------------------------------------------- + +class Skeleton_LoggingEvent(RegularEvent, PopulationScopeEventMixin): + def __init__(self, module): + """Produce a summary of the numbers of people with respect to the action of this module. + This is a regular event that can output current states of people or cumulative events since last logging event. + """ + + # run this event every year + self.repeat = 12 + super().__init__(module, frequency=DateOffset(months=self.repeat)) + assert isinstance(module, MaternalNewbornHealthCohort) + + def apply(self, population): + # Make some summary statistics + + dict_to_output = { + 'Metric_One': 1.0, + 'Metric_Two': 2.0 + } + + logger.info(key='summary_12m', data=dict_to_output) + + +# --------------------------------------------------------------------------------------------------------- +# HEALTH SYSTEM INTERACTION EVENTS +# +# Here are all the different Health System Interactions Events that this module will use. +# --------------------------------------------------------------------------------------------------------- + +class HSI_Skeleton_Example_Interaction(HSI_Event, IndividualScopeEventMixin): + """This is a Health System Interaction Event. An interaction with the healthsystem are encapsulated in events + like this. + It must begin HSI__Description + """ + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + assert isinstance(module, MaternalNewbornHealthCohort) + + # Define the appointments types needed + the_appt_footprint = self.make_appt_footprint({'Over5OPD': 1}) # This requires one adult out-patient appt. + + # Define the facilities at which this event can occur (only one is allowed) + # Choose from: list(pd.unique(self.sim.modules['HealthSystem'].parameters['Facilities_For_Each_District'] + # ['Facility_Level'])) + the_accepted_facility_level = 0 + + # Define the necessary information for an HSI + self.TREATMENT_ID = 'Skeleton_Example_Interaction' # This must begin with the module name + self.EXPECTED_APPT_FOOTPRINT = the_appt_footprint + self.ACCEPTED_FACILITY_LEVEL = the_accepted_facility_level + self.ALERT_OTHER_DISEASES = [] + + def apply(self, person_id, squeeze_factor): + """ + Do the action that take place in this health system interaction, in light of prevailing conditions in the + healthcare system: + + * squeeze_factor (an argument provided to the event) indicates the extent to which this HSI_Event is being + run in the context of an over-burdened healthcare facility. + * bed_days_allocated_to_this_event (a property of the event) indicates the number and types of bed-days + that have been allocated to this event. + + Can return an updated APPT_FOOTPRINT if this differs from the declaration in self.EXPECTED_APPT_FOOTPRINT + + To request consumables use - self.get_all_consumables(item_codes=my_item_codes) + """ + pass + + def did_not_run(self): + """ + Do any action that is necessary each time when the health system interaction is not run. + This is called each day that the HSI is 'due' but not run due to insufficient health system capabilities. + Return False to cause this HSI event not to be rescheduled and to therefore never be run. + (Returning nothing or True will cause this event to be rescheduled for the next day.) + """ + pass + + def never_ran(self): + """ + Do any action that is necessary when it is clear that the HSI event will never be run. This will occur if it + has not run and the simulation date has passed the date 'tclose' by which the event was scheduled to have + occurred. + Do not return anything. + """ + pass diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py new file mode 100644 index 0000000000..a47fd7753e --- /dev/null +++ b/tests/test_mnh_cohort.py @@ -0,0 +1,54 @@ +import os +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest + +from tlo import DAYS_IN_MONTH, DAYS_IN_YEAR, Date, Simulation +from tlo.analysis.utils import parse_log_file +from tlo.lm import LinearModel, LinearModelType +from tlo.methods import mnh_cohort_module +from tlo.methods.fullmodel import fullmodel + + +# The resource files +try: + resourcefilepath = Path(os.path.dirname(__file__)) / '../resources' +except NameError: + # running interactively + resourcefilepath = Path('./resources') + +start_date = Date(2010, 1, 1) + + +def check_dtypes(simulation): + # check types of columns + df = simulation.population.props + orig = simulation.population.new_row + assert (df.dtypes == orig.dtypes).all() + + +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", "directory": tmpdir}) + + register_modules(sim) + sim.make_initial_population(n=1000) + sim.simulate(end_date=Date(2011, 1, 1)) + check_dtypes(sim) + +# 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!! From 460f870542c2451d806a95ebe58142c6fa3c2ebd Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 31 May 2024 15:04:21 +0100 Subject: [PATCH 03/38] updates to mnh module --- src/tlo/methods/mnh_cohort_module.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/src/tlo/methods/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py index da8c2e8598..487e1e8f57 100644 --- a/src/tlo/methods/mnh_cohort_module.py +++ b/src/tlo/methods/mnh_cohort_module.py @@ -75,13 +75,15 @@ def initialise_simulation(self, sim): """ # cohort_prop_df = pd.read_excel(Path(self.resourcefilepath) / 'ResourceFile_PregnancyCohort.xlsx') - # from tlo.analysis.utils import parse_log_file - # cohort_prop_df = parse_log_file( - # '/Users/j_collins/PycharmProjects/TLOmodel/outputs/sejjj49@ucl.ac.uk/fullmodel_200k_cohort-2024-04-24T072206Z/0/0/fullmodel_200k_cohort__2024-04-24T072516.log', - # level=logging.DEBUG)['tlo.methods.contraception'] - # - # cohort_prop_df_for_pop = cohort_prop_df.loc[0:(len(self.sim.population.props))-1] - # self.sim.population.props = cohort_prop_df_for_pop + from tlo.analysis.utils import parse_log_file + t = parse_log_file( + '/Users/j_collins/PycharmProjects/TLOmodel/outputs/sejjj49@ucl.ac.uk/fullmodel_200k_cohort-2024-04-24T072206Z/0/0/fullmodel_200k_cohort__2024-04-24T072516.log', + level=logging.DEBUG)['tlo.methods.contraception'] + + cohort_prop_df2 = t['properties_of_pregnant_person'] + cohort_prop_df_for_pop = cohort_prop_df2.loc[0:(len(self.sim.population.props))-1] + self.sim.population.props = cohort_prop_df_for_pop + # # # todo: pd.NaT values are not carried over when excel file was created. # # df = self.sim.population.props @@ -89,10 +91,10 @@ def initialise_simulation(self, sim): # # for column in df.columns: # # df.loc[population.index, column] = cohort_prop_df_for_pop[column] # - # df = self.sim.population.props - # population = df.loc[df.is_alive] - # df.loc[population.index, 'date_of_last_pregnancy'] = sim.start_date - # df.loc[population.index, 'co_contraception'] = "not_using" + df = self.sim.population.props + population = df.loc[df.is_alive] + df.loc[population.index, 'date_of_last_pregnancy'] = sim.start_date + df.loc[population.index, 'co_contraception'] = "not_using" def on_birth(self, mother_id, child_id): From 878695d60693228164e693b4322d0f164f909f0f Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 10 Jun 2024 12:57:28 +0100 Subject: [PATCH 04/38] updates to mnh module --- src/tlo/methods/mnh_cohort_module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py index 487e1e8f57..e96b20ed27 100644 --- a/src/tlo/methods/mnh_cohort_module.py +++ b/src/tlo/methods/mnh_cohort_module.py @@ -83,9 +83,10 @@ def initialise_simulation(self, sim): cohort_prop_df2 = t['properties_of_pregnant_person'] cohort_prop_df_for_pop = cohort_prop_df2.loc[0:(len(self.sim.population.props))-1] self.sim.population.props = cohort_prop_df_for_pop - + self.sim.population.props.index.name = 'person' # # # todo: pd.NaT values are not carried over when excel file was created. + # todo replace with pickles # # df = self.sim.population.props # # population = df.loc[df.is_alive] # # for column in df.columns: From 12a56a7249c385d3251d38929ce7b5e1a09a5552 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 26 Jun 2024 12:27:33 +0100 Subject: [PATCH 05/38] update to cohort and util.py --- .../cohort_interventions_scenario.py | 48 ++++ src/tlo/methods/mnh_cohort_module.py | 208 +++++------------- src/tlo/util.py | 9 +- tests/test_mnh_cohort.py | 5 +- 4 files changed, 108 insertions(+), 162 deletions(-) create mode 100644 src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py 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..15c722b687 --- /dev/null +++ b/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py @@ -0,0 +1,48 @@ +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 = 661184 + self.start_date = Date(2010, 1, 1) + self.end_date = Date(2031, 1, 1) + self.pop_size = 250_000 + self.number_of_draws = 1 + self.runs_per_draw = 20 + + def log_configuration(self): + return { + 'filename': 'intervention_scenario_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), + mnh_cohort_module.MaternalNewbornHealthCohort(resourcefilepath=self.resources)] + + def draw_parameters(self, draw_number, rng): + return { + 'MaternalNewbornHealthCohort': {'blocked_intervention': 'screening (direct)'}, + } + + +if __name__ == '__main__': + from tlo.cli import scenario_run + scenario_run([__file__]) diff --git a/src/tlo/methods/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py index e96b20ed27..61dddc3c0c 100644 --- a/src/tlo/methods/mnh_cohort_module.py +++ b/src/tlo/methods/mnh_cohort_module.py @@ -3,10 +3,8 @@ import numpy as np import pandas as pd from tlo import DateOffset, Module, Parameter, Property, Types, logging -from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.methods import Metadata -from tlo.methods.causes import Cause -from tlo.methods.hsi_event import HSI_Event +from tlo.analysis.utils import parse_log_file logger = logging.getLogger(__name__) @@ -64,7 +62,31 @@ def initialise_population(self, population): :param population: the population of individuals """ - pass + + # TODO: CURRENT ISSUE - INDIVIDUALS IN THE POPULATION ARE SCHEDULED HSIs IN INITIALISE_POP/SIM BY OTHER MODULES, + # THEIR PROPERTIES ARE THEN OVER WRITTEN BY THIS MODULE AND ITS CRASHING HSIs + + log_file = parse_log_file( + '/Users/j_collins/PycharmProjects/TLOmodel/outputs/sejjj49@ucl.ac.uk/' + 'fullmodel_200k_cohort-2024-04-24T072206Z/0/0/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))] + + preg_pop = all_pregnancies.loc[0:(len(self.sim.population.props))-1] + + props_dtypes = self.sim.population.props.dtypes + preg_pop_final = preg_pop.astype(props_dtypes.to_dict()) + preg_pop_final.index.name = 'person' + + self.sim.population.props = preg_pop_final + + 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. @@ -74,29 +96,31 @@ def initialise_simulation(self, sim): It is a good place to add initial events to the event queue. """ - # cohort_prop_df = pd.read_excel(Path(self.resourcefilepath) / 'ResourceFile_PregnancyCohort.xlsx') - from tlo.analysis.utils import parse_log_file - t = parse_log_file( - '/Users/j_collins/PycharmProjects/TLOmodel/outputs/sejjj49@ucl.ac.uk/fullmodel_200k_cohort-2024-04-24T072206Z/0/0/fullmodel_200k_cohort__2024-04-24T072516.log', - level=logging.DEBUG)['tlo.methods.contraception'] - - cohort_prop_df2 = t['properties_of_pregnant_person'] - cohort_prop_df_for_pop = cohort_prop_df2.loc[0:(len(self.sim.population.props))-1] - self.sim.population.props = cohort_prop_df_for_pop - self.sim.population.props.index.name = 'person' + pass + # # cohort_prop_df = pd.read_excel(Path(self.resourcefilepath) / 'ResourceFile_PregnancyCohort.xlsx') + # from tlo.analysis.utils import parse_log_file, load_pickled_dataframes, get_scenario_outputs # - # # todo: pd.NaT values are not carried over when excel file was created. - # todo replace with pickles - # # df = self.sim.population.props - # # population = df.loc[df.is_alive] - # # for column in df.columns: - # # df.loc[population.index, column] = cohort_prop_df_for_pop[column] + # log_file = parse_log_file( + # '/Users/j_collins/PycharmProjects/TLOmodel/outputs/sejjj49@ucl.ac.uk/' + # 'fullmodel_200k_cohort-2024-04-24T072206Z/0/0/fullmodel_200k_cohort__2024-04-24T072516.log', + # level=logging.DEBUG)['tlo.methods.contraception'] # - df = self.sim.population.props - population = df.loc[df.is_alive] - df.loc[population.index, 'date_of_last_pregnancy'] = sim.start_date - df.loc[population.index, 'co_contraception'] = "not_using" - + # 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))] + # + # preg_pop = all_pregnancies.loc[0:(len(self.sim.population.props))-1] + # + # props_dtypes = self.sim.population.props.dtypes + # preg_pop_final = preg_pop.astype(props_dtypes.to_dict()) + # preg_pop_final.index.name = 'person' + # + # self.sim.population.props = preg_pop_final + # + # df = self.sim.population.props + # population = df.loc[df.is_alive] + # df.loc[population.index, 'date_of_last_pregnancy'] = sim.start_date + # df.loc[population.index, 'co_contraception'] = "not_using" def on_birth(self, mother_id, child_id): """Initialise our properties for a newborn individual. @@ -106,7 +130,7 @@ def on_birth(self, mother_id, child_id): :param mother_id: the mother for this child :param child_id: the new child """ - raise NotImplementedError + pass def report_daly_values(self): """ @@ -120,141 +144,11 @@ def report_daly_values(self): df = self.sim.population.props return pd.Series(index=df.index[df.is_alive],data=0.0) """ - raise NotImplementedError + pass def on_hsi_alert(self, person_id, treatment_id): """ This is called whenever there is an HSI event commissioned by one of the other disease modules. """ - raise NotImplementedError - - -# --------------------------------------------------------------------------------------------------------- -# DISEASE MODULE EVENTS -# -# These are the events which drive the simulation of the disease. It may be a regular event that updates -# the status of all the population of subsections of it at one time. There may also be a set of events -# that represent disease events for particular persons. -# --------------------------------------------------------------------------------------------------------- - -class Skeleton_Event(RegularEvent, PopulationScopeEventMixin): - """A skeleton class for an event - - Regular events automatically reschedule themselves at a fixed frequency, - and thus implement discrete timestep type behaviour. The frequency is - specified when calling the base class constructor in our __init__ method. - """ - - def __init__(self, module): - """One line summary here - - We need to pass the frequency at which we want to occur to the base class - constructor using super(). We also pass the module that created this event, - so that random number generators can be scoped per-module. - - :param module: the module that created this event - """ - super().__init__(module, frequency=DateOffset(months=1)) - assert isinstance(module, MaternalNewbornHealthCohort) - - def apply(self, population): - """Apply this event to the population. - - :param population: the current population - """ - raise NotImplementedError - - -# --------------------------------------------------------------------------------------------------------- -# LOGGING EVENTS -# -# Put the logging events here. There should be a regular logger outputting current states of the -# population. There may also be a logging event that is driven by particular events. -# --------------------------------------------------------------------------------------------------------- - -class Skeleton_LoggingEvent(RegularEvent, PopulationScopeEventMixin): - def __init__(self, module): - """Produce a summary of the numbers of people with respect to the action of this module. - This is a regular event that can output current states of people or cumulative events since last logging event. - """ - - # run this event every year - self.repeat = 12 - super().__init__(module, frequency=DateOffset(months=self.repeat)) - assert isinstance(module, MaternalNewbornHealthCohort) - - def apply(self, population): - # Make some summary statistics - - dict_to_output = { - 'Metric_One': 1.0, - 'Metric_Two': 2.0 - } - - logger.info(key='summary_12m', data=dict_to_output) - - -# --------------------------------------------------------------------------------------------------------- -# HEALTH SYSTEM INTERACTION EVENTS -# -# Here are all the different Health System Interactions Events that this module will use. -# --------------------------------------------------------------------------------------------------------- - -class HSI_Skeleton_Example_Interaction(HSI_Event, IndividualScopeEventMixin): - """This is a Health System Interaction Event. An interaction with the healthsystem are encapsulated in events - like this. - It must begin HSI__Description - """ - - def __init__(self, module, person_id): - super().__init__(module, person_id=person_id) - assert isinstance(module, MaternalNewbornHealthCohort) - - # Define the appointments types needed - the_appt_footprint = self.make_appt_footprint({'Over5OPD': 1}) # This requires one adult out-patient appt. - - # Define the facilities at which this event can occur (only one is allowed) - # Choose from: list(pd.unique(self.sim.modules['HealthSystem'].parameters['Facilities_For_Each_District'] - # ['Facility_Level'])) - the_accepted_facility_level = 0 - - # Define the necessary information for an HSI - self.TREATMENT_ID = 'Skeleton_Example_Interaction' # This must begin with the module name - self.EXPECTED_APPT_FOOTPRINT = the_appt_footprint - self.ACCEPTED_FACILITY_LEVEL = the_accepted_facility_level - self.ALERT_OTHER_DISEASES = [] - - def apply(self, person_id, squeeze_factor): - """ - Do the action that take place in this health system interaction, in light of prevailing conditions in the - healthcare system: - - * squeeze_factor (an argument provided to the event) indicates the extent to which this HSI_Event is being - run in the context of an over-burdened healthcare facility. - * bed_days_allocated_to_this_event (a property of the event) indicates the number and types of bed-days - that have been allocated to this event. - - Can return an updated APPT_FOOTPRINT if this differs from the declaration in self.EXPECTED_APPT_FOOTPRINT - - To request consumables use - self.get_all_consumables(item_codes=my_item_codes) - """ - pass - - def did_not_run(self): - """ - Do any action that is necessary each time when the health system interaction is not run. - This is called each day that the HSI is 'due' but not run due to insufficient health system capabilities. - Return False to cause this HSI event not to be rescheduled and to therefore never be run. - (Returning nothing or True will cause this event to be rescheduled for the next day.) - """ - pass - - def never_ran(self): - """ - Do any action that is necessary when it is clear that the HSI event will never be run. This will occur if it - has not run and the simulation date has passed the date 'tclose' by which the event was scheduled to have - occurred. - Do not return anything. - """ pass diff --git a/src/tlo/util.py b/src/tlo/util.py index cafd04f738..5b6e18fbd9 100644 --- a/src/tlo/util.py +++ b/src/tlo/util.py @@ -112,10 +112,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 index a47fd7753e..521f525c61 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -33,8 +33,9 @@ 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), + sim.register(mnh_cohort_module.MaternalNewbornHealthCohort(resourcefilepath=resourcefilepath), + *fullmodel(resourcefilepath=resourcefilepath), + ) def test_run_sim_with_mnh_cohort(tmpdir, seed): From 973cbff7c339e5bef7418ca24bf4b69edc988554 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 24 Jul 2024 08:16:03 +0100 Subject: [PATCH 06/38] update to cohort and util.py --- tests/test_mnh_cohort.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py index 521f525c61..bc2f14d1bb 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -1,13 +1,7 @@ import os from pathlib import Path -import numpy as np -import pandas as pd -import pytest - -from tlo import DAYS_IN_MONTH, DAYS_IN_YEAR, Date, Simulation -from tlo.analysis.utils import parse_log_file -from tlo.lm import LinearModel, LinearModelType +from tlo import Date, Simulation, logging from tlo.methods import mnh_cohort_module from tlo.methods.fullmodel import fullmodel @@ -39,7 +33,8 @@ def register_modules(sim): ) def test_run_sim_with_mnh_cohort(tmpdir, seed): - sim = Simulation(start_date=start_date, seed=seed, log_config={"filename": "log", "directory": tmpdir}) + 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=1000) From 06246c479110502a3777f027c1d498edf49e3c60 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 2 Oct 2024 16:06:57 +0100 Subject: [PATCH 07/38] update to cohort --- src/tlo/methods/mnh_cohort_module.py | 41 ++++++++----------------- tests/test_mnh_cohort.py | 45 ++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 41 deletions(-) diff --git a/src/tlo/methods/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py index 61dddc3c0c..826930d161 100644 --- a/src/tlo/methods/mnh_cohort_module.py +++ b/src/tlo/methods/mnh_cohort_module.py @@ -5,6 +5,7 @@ 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__) @@ -63,9 +64,6 @@ def initialise_population(self, population): :param population: the population of individuals """ - # TODO: CURRENT ISSUE - INDIVIDUALS IN THE POPULATION ARE SCHEDULED HSIs IN INITIALISE_POP/SIM BY OTHER MODULES, - # THEIR PROPERTIES ARE THEN OVER WRITTEN BY THIS MODULE AND ITS CRASHING HSIs - log_file = parse_log_file( '/Users/j_collins/PycharmProjects/TLOmodel/outputs/sejjj49@ucl.ac.uk/' 'fullmodel_200k_cohort-2024-04-24T072206Z/0/0/fullmodel_200k_cohort__2024-04-24T072516.log', @@ -88,6 +86,7 @@ def initialise_population(self, population): 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. @@ -96,31 +95,17 @@ def initialise_simulation(self, sim): It is a good place to add initial events to the event queue. """ - pass - # # cohort_prop_df = pd.read_excel(Path(self.resourcefilepath) / 'ResourceFile_PregnancyCohort.xlsx') - # from tlo.analysis.utils import parse_log_file, load_pickled_dataframes, get_scenario_outputs - # - # log_file = parse_log_file( - # '/Users/j_collins/PycharmProjects/TLOmodel/outputs/sejjj49@ucl.ac.uk/' - # 'fullmodel_200k_cohort-2024-04-24T072206Z/0/0/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))] - # - # preg_pop = all_pregnancies.loc[0:(len(self.sim.population.props))-1] - # - # props_dtypes = self.sim.population.props.dtypes - # preg_pop_final = preg_pop.astype(props_dtypes.to_dict()) - # preg_pop_final.index.name = 'person' - # - # self.sim.population.props = preg_pop_final - # - # df = self.sim.population.props - # population = df.loc[df.is_alive] - # df.loc[population.index, 'date_of_last_pregnancy'] = sim.start_date - # df.loc[population.index, 'co_contraception'] = "not_using" + df = self.sim.population.props + + sim.modules['HealthSystem'].HSI_EVENT_QUEUE.clear() + + for item in self.sim.event_queue.queue: + if isinstance(item[3], IndividualScopeEventMixin): + self.sim.event_queue.queue.remove(item) + + for person in df.index: + self.sim.modules['Labour'].set_date_of_labour(person) + def on_birth(self, mother_id, child_id): """Initialise our properties for a newborn individual. diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py index bc2f14d1bb..8cee5e76d0 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -4,7 +4,7 @@ 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: @@ -16,30 +16,49 @@ start_date = Date(2010, 1, 1) -def check_dtypes(simulation): - # check types of columns - df = simulation.population.props - orig = simulation.population.new_row - assert (df.dtypes == orig.dtypes).all() - - 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(mnh_cohort_module.MaternalNewbornHealthCohort(resourcefilepath=resourcefilepath), - *fullmodel(resourcefilepath=resourcefilepath), - + 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=1000) - sim.simulate(end_date=Date(2011, 1, 1)) - check_dtypes(sim) + sim.simulate(end_date=Date(2024, 1, 1)) + + 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 = ['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 + mmr = (len(dir_mat_deaths) + indir_mat_deaths) / live_births * 100_000 + + + + # df = sim.population.props + # 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}) From 86d130a1af311a833274965678a05bdcc1fe329e Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 2 Oct 2024 16:21:38 +0100 Subject: [PATCH 08/38] update to cohort --- tests/test_mnh_cohort.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py index 8cee5e76d0..4502393967 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -13,7 +13,7 @@ # running interactively resourcefilepath = Path('./resources') -start_date = Date(2010, 1, 1) +start_date = Date(2024, 1, 1) def register_modules(sim): @@ -31,7 +31,7 @@ def test_run_sim_with_mnh_cohort(tmpdir, seed): register_modules(sim) sim.make_initial_population(n=1000) - sim.simulate(end_date=Date(2024, 1, 1)) + sim.simulate(end_date=Date(2025, 1, 1)) output= parse_log_file(sim.log_filepath) live_births = len(output['tlo.methods.demography']['on_birth']) From 3a90ab79c223203b63405afecfdc87adece59ec7 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 4 Oct 2024 09:22:24 +0100 Subject: [PATCH 09/38] small updates to prevent scheduling of events in the past when sim starts past 2010 --- src/tlo/methods/epi.py | 6 +++++- src/tlo/methods/labour.py | 5 +++-- src/tlo/methods/mnh_cohort_module.py | 7 ++++--- src/tlo/methods/pregnancy_supervisor.py | 12 +++++++++--- src/tlo/methods/schisto.py | 3 ++- tests/test_mnh_cohort.py | 15 ++++++++++++--- 6 files changed, 35 insertions(+), 13 deletions(-) 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/labour.py b/src/tlo/methods/labour.py index 35081b7d27..92725cbbe4 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -878,8 +878,9 @@ def initialise_simulation(self, sim): sim.schedule_event(LabourLoggingEvent(self), sim.date + DateOffset(days=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 = [] diff --git a/src/tlo/methods/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py index 826930d161..8430d3aba7 100644 --- a/src/tlo/methods/mnh_cohort_module.py +++ b/src/tlo/methods/mnh_cohort_module.py @@ -99,9 +99,10 @@ def initialise_simulation(self, sim): sim.modules['HealthSystem'].HSI_EVENT_QUEUE.clear() - for item in self.sim.event_queue.queue: - if isinstance(item[3], IndividualScopeEventMixin): - self.sim.event_queue.queue.remove(item) + 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 for person in df.index: self.sim.modules['Labour'].set_date_of_labour(person) diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 7dd8819ab6..97e98858df 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -523,13 +523,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 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/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py index 4502393967..35b662b3fc 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -30,14 +30,14 @@ def test_run_sim_with_mnh_cohort(tmpdir, seed): "*": logging.DEBUG},"directory": tmpdir}) register_modules(sim) - sim.make_initial_population(n=1000) + sim.make_initial_population(n=5000) sim.simulate(end_date=Date(2025, 1, 1)) 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 = ['tlo.methods.demography.detail']['properties_of_deceased_persons'] + 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']) & @@ -50,9 +50,18 @@ def test_run_sim_with_mnh_cohort(tmpdir, seed): (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 = (len(dir_mat_deaths) + indir_mat_deaths) / live_births * 100_000 + 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}') From 930973e4b9135159a70a0964bdb7c1556efa799a Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 4 Oct 2024 14:06:51 +0100 Subject: [PATCH 10/38] new calibration file. prevent new pregnancies during cohort run. --- .../local_run_cohort_calibration.py | 111 ++++++++++++++++++ src/tlo/methods/mnh_cohort_module.py | 8 +- 2 files changed, 118 insertions(+), 1 deletion(-) create mode 100644 src/scripts/maternal_perinatal_analyses/cohort_analysis/local_run_cohort_calibration.py 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..c22549d3cf --- /dev/null +++ b/src/scripts/maternal_perinatal_analyses/cohort_analysis/local_run_cohort_calibration.py @@ -0,0 +1,111 @@ +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=123, + 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 + len(output['tlo.methods.contraception']['pregnancy']) +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'] + +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 + + len(output['tlo.methods.pregnancy_supervisor']['antenatal_stillbirth']) + + len(output['tlo.methods.labour']['intrapartum_stillbirth'])) + +# 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/tlo/methods/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py index 8430d3aba7..4007d11a31 100644 --- a/src/tlo/methods/mnh_cohort_module.py +++ b/src/tlo/methods/mnh_cohort_module.py @@ -97,17 +97,23 @@ def initialise_simulation(self, sim): """ df = self.sim.population.props + # Clear HSI queue for events scheduled during initialisation sim.modules['HealthSystem'].HSI_EVENT_QUEUE.clear() + # Clear HSI 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 + 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): """Initialise our properties for a newborn individual. From f1354c5cc6ca1ad0edb8f830954655f9989b6ad1 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 7 Oct 2024 08:54:59 +0100 Subject: [PATCH 11/38] update scenario for azure test --- .../cohort_interventions_scenario.py | 13 ++++++------- .../cohort_analysis/local_run_cohort_calibration.py | 11 ++++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) 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 index 15c722b687..30b5ebfc09 100644 --- a/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py +++ b/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py @@ -7,16 +7,16 @@ class BaselineScenario(BaseScenario): def __init__(self): super().__init__() - self.seed = 661184 - self.start_date = Date(2010, 1, 1) - self.end_date = Date(2031, 1, 1) - self.pop_size = 250_000 + self.seed = 537184 + self.start_date = Date(2024, 1, 1) + self.end_date = Date(2025, 1, 1) + self.pop_size = 5000 self.number_of_draws = 1 - self.runs_per_draw = 20 + self.runs_per_draw = 10 def log_configuration(self): return { - 'filename': 'intervention_scenario_test', 'directory': './outputs', + 'filename': 'cohort_test', 'directory': './outputs', "custom_levels": { "*": logging.WARNING, "tlo.methods.demography": logging.INFO, @@ -39,7 +39,6 @@ def modules(self): def draw_parameters(self, draw_number, rng): return { - 'MaternalNewbornHealthCohort': {'blocked_intervention': 'screening (direct)'}, } 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 index c22549d3cf..a93cdfb61a 100644 --- 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 @@ -14,7 +14,7 @@ population_size = 2000 sim = Simulation(start_date=Date(2024, 1, 1), - seed=123, + seed=456, log_config={"filename": "log_cohort_calibration", "custom_levels": {"*": logging.DEBUG}, "directory": outputpath}) @@ -58,7 +58,7 @@ 'PPH']) # total_pregnancies = population_size -total_pregnancies = 2000 + len(output['tlo.methods.contraception']['pregnancy']) +total_pregnancies = 2000 total_births = len(output['tlo.methods.demography']['on_birth']) prop_live_births = (total_births/total_pregnancies) * 100 @@ -89,13 +89,18 @@ 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 + + (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 From 3c0d4856b181c73ed85e99564dc8b31d6aff4c0e Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 7 Oct 2024 09:02:41 +0100 Subject: [PATCH 12/38] update scenario for azure test --- src/tlo/methods/mnh_cohort_module.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py index 4007d11a31..0bbae2c854 100644 --- a/src/tlo/methods/mnh_cohort_module.py +++ b/src/tlo/methods/mnh_cohort_module.py @@ -63,10 +63,10 @@ def initialise_population(self, population): :param population: the population of individuals """ - + # todo: we need to think of a better way to do this log_file = parse_log_file( - '/Users/j_collins/PycharmProjects/TLOmodel/outputs/sejjj49@ucl.ac.uk/' - 'fullmodel_200k_cohort-2024-04-24T072206Z/0/0/fullmodel_200k_cohort__2024-04-24T072516.log', + '/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[ From d51ff89bab69e0fb057042123aa31993d433a40f Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 7 Oct 2024 11:42:51 +0100 Subject: [PATCH 13/38] make cohort df come from excel --- ...rceFile_All2024PregnanciesCohortModel.xlsx | 3 +++ src/tlo/methods/mnh_cohort_module.py | 27 ++++++++++++------- tests/test_mnh_cohort.py | 2 +- 3 files changed, 22 insertions(+), 10 deletions(-) create mode 100644 resources/maternal cohort/ResourceFile_All2024PregnanciesCohortModel.xlsx 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/tlo/methods/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py index 0bbae2c854..f5e19c6767 100644 --- a/src/tlo/methods/mnh_cohort_module.py +++ b/src/tlo/methods/mnh_cohort_module.py @@ -63,22 +63,31 @@ def initialise_population(self, population): :param population: the population of individuals """ - # todo: we need to think of a better way to do this - 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))] + # 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))] - preg_pop = all_pregnancies.loc[0:(len(self.sim.population.props))-1] + all_preg_df = pd.read_excel(Path(f'{self.resourcefilepath}/maternal cohort') / + 'ResourceFile_All2024PregnanciesCohortModel.xlsx') + preg_pop = all_preg_df.loc[0:(len(self.sim.population.props))-1] props_dtypes = self.sim.population.props.dtypes + preg_pop_final = preg_pop.astype(props_dtypes.to_dict()) preg_pop_final.index.name = 'person' + + 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))] + self.sim.population.props = preg_pop_final df = self.sim.population.props diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py index 35b662b3fc..df9bc58e15 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -30,7 +30,7 @@ def test_run_sim_with_mnh_cohort(tmpdir, seed): "*": logging.DEBUG},"directory": tmpdir}) register_modules(sim) - sim.make_initial_population(n=5000) + sim.make_initial_population(n=2500) sim.simulate(end_date=Date(2025, 1, 1)) output= parse_log_file(sim.log_filepath) From 0c7aa4169b8def292aa93f8f95d3a94e80110419 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 7 Oct 2024 13:38:49 +0100 Subject: [PATCH 14/38] created mnh_outcome_logger to eventually replace logging for complication incidence --- src/tlo/methods/labour.py | 19 +++ src/tlo/methods/newborn_outcomes.py | 12 ++ src/tlo/methods/postnatal_supervisor.py | 14 ++ src/tlo/methods/pregnancy_helper_functions.py | 78 +++++++++++ src/tlo/methods/pregnancy_supervisor.py | 126 +++++++++++++----- 5 files changed, 215 insertions(+), 34 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 35081b7d27..02fcd65630 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1286,6 +1286,8 @@ def set_intrapartum_complications(self, individual_id, complication): 'type': f'{complication}', 'timing': 'intrapartum'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[complication] += 1 + if complication == 'obstruction_cpd': mni[individual_id]['cpd'] = True @@ -1296,6 +1298,8 @@ def set_intrapartum_complications(self, individual_id, complication): 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'], @@ -1308,6 +1312,8 @@ def set_intrapartum_complications(self, individual_id, complication): 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', @@ -1315,6 +1321,7 @@ def set_intrapartum_complications(self, individual_id, complication): 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 @@ -1324,6 +1331,7 @@ def set_intrapartum_complications(self, individual_id, complication): 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 @@ -1332,6 +1340,7 @@ def set_intrapartum_complications(self, individual_id, complication): 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): """ @@ -1387,6 +1396,7 @@ def set_postpartum_complications(self, individual_id, complication): 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': @@ -2427,6 +2437,7 @@ def apply(self, individual_id): 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' @@ -2438,6 +2449,7 @@ def apply(self, individual_id): 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]: @@ -2446,6 +2458,7 @@ def apply(self, individual_id): 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'] @@ -2615,6 +2628,7 @@ def apply(self, individual_id): 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') @@ -2731,6 +2745,7 @@ def apply(self, 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: @@ -2821,10 +2836,13 @@ def apply(self, mother_id): 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') @@ -2980,6 +2998,7 @@ def apply(self, person_id, squeeze_factor): 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 diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 3691bc6003..b0e8d461bd 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -525,26 +525,31 @@ def apply_risk_of_congenital_anomaly(self, child_id): 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): """ @@ -561,6 +566,7 @@ def apply_risk_of_neonatal_infection_and_sepsis(self, child_id): 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): """ @@ -594,6 +600,7 @@ def apply_risk_of_encephalopathy(self, child_id, timing): 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': @@ -619,6 +626,7 @@ def apply_risk_of_preterm_respiratory_distress_syndrome(self, child_id): 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): """ @@ -641,6 +649,7 @@ def apply_risk_of_not_breathing_at_birth(self, child_id): 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): """ @@ -1158,13 +1167,16 @@ def on_birth(self, mother_id, child_id): (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..428be94aaa 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -358,6 +358,7 @@ def further_on_birth_postnatal_supervisor(self, mother_id): 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[ @@ -503,6 +504,7 @@ def onset(eq): 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 @@ -518,6 +520,7 @@ def onset(eq): 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 @@ -597,6 +600,7 @@ def log_new_progressed_cases(disease): 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 @@ -642,6 +646,7 @@ def log_new_progressed_cases(disease): logger.info(key='maternal_complication', data={'person': person, 'type': 'mild_pre_eclamp', 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['mild_pre_eclamp'] += 1 # -------------------------------- RISK OF GESTATIONAL HYPERTENSION -------------------------------------- gest_hypertension = self.apply_linear_model( @@ -655,6 +660,7 @@ def log_new_progressed_cases(disease): logger.info(key='maternal_complication', data={'person': person, 'type': 'mild_gest_htn', 'timing': 'postnatal'}) + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['mild_gest_htn'] += 1 # -------------------------------- RISK OF DEATH SEVERE HYPERTENSION ------------------------------------------ # Risk of death is applied to women with severe hypertensive disease @@ -747,6 +753,7 @@ def apply_risk_of_neonatal_complications_in_week_one(self, child_id, mother_id): 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): """ @@ -774,6 +781,7 @@ def set_postnatal_complications_neonates(self, upper_and_lower_day_limits): 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( @@ -1030,6 +1038,7 @@ def apply(self, individual_id): 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: @@ -1049,6 +1058,7 @@ def apply(self, individual_id): 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... @@ -1117,6 +1127,8 @@ def log_new_progressed_cases(disease): 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 @@ -1139,6 +1151,7 @@ def log_new_progressed_cases(disease): 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[[ @@ -1149,6 +1162,7 @@ def log_new_progressed_cases(disease): 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..4866d78ce7 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -8,6 +8,84 @@ from tlo import logging +def generate_mnh_outcome_counter(): + outcome_list = ['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', + 'gest_diab', + 'clinical_chorioamnionitis', + 'antenatal_stillbirth', + 'ectopic_unruptured', + 'multiple_pregnancy', + 'placenta_praevia', + 'ectopic_ruptured', + 'syphilis', + 'placental_abruption', + 'severe_antepartum_haemorrhage', + 'mild_mod_antepartum_haemorrhage', + 'PROM' + 'obstruction_cpd', + 'obstruction_malpos_malpres', + 'obstruction_other', + 'obstructed_labour', + 'sepsis_intrapartum', + 'uterine_rupture', 'early_preterm_labour', 'late_preterm_labour', 'post_term_labour', + 'intrapartum_stillbirth', + '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', + 'gest_diab', + 'mild_pre_eclamp', + 'mild_gest_htn' + 'severe_pre_eclamp', + 'eclampsia', + 'severe_gest_htn', + 'placental_abruption', + 'severe_antepartum_haemorrhage', + 'mild_mod_antepartum_haemorrhage', + 'PROM' + 'clinical_chorioamnionitis', + 'antenatal_stillbirth', + 'ectopic_unruptured', + 'multiple_pregnancy', + 'placenta_praevia', + 'ectopic_ruptured', + 'syphilis', + 'obstruction_cpd', + 'obstruction_malpos_malpres', + 'obstruction_other', + 'obstructed_labour', + 'sepsis_intrapartum', + 'uterine_rupture', 'early_preterm_labour', 'late_preterm_labour', 'post_term_labour', + 'intrapartum_stillbirth', 'vesicovaginal_fistula', 'rectovaginal_fistula', + 'secondary_postpartum_haemorrhage', 'pph_uterine_atony', 'pph_retained_placenta', 'pph_other', + 'primary_postpartum_haemorrhage', 'sepsis_endometritis', 'sepsis_urinary_tract', + 'sepsis_skin_soft_tissue', 'sepsis_postnatal', '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', + ] + + mnh_outcome_counter = {k: 0 for k in outcome_list} + + return mnh_outcome_counter def get_list_of_items(self, item_list): """ diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 7dd8819ab6..e82eb71fad 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -62,6 +62,9 @@ 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 + self.mnh_outcome_counter = pregnancy_helper_functions.generate_mnh_outcome_counter() + INIT_DEPENDENCIES = {'Demography'} OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden', 'Malaria', 'CardioMetabolicDisorders', 'Hiv'} @@ -970,6 +973,7 @@ def do_after_abortion(self, individual_id, type_abortion): 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) @@ -1000,6 +1004,8 @@ def do_after_abortion(self, individual_id, type_abortion): '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}') def apply_risk_of_abortion_complications(self, individual_id, cause): @@ -1019,6 +1025,7 @@ def apply_risk_of_abortion_complications(self, individual_id, cause): 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') @@ -1027,6 +1034,7 @@ def apply_risk_of_abortion_complications(self, individual_id, cause): 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') @@ -1035,12 +1043,14 @@ def apply_risk_of_abortion_complications(self, individual_id, cause): 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) @@ -1103,6 +1113,8 @@ def apply_risk_of_gestational_diabetes(self, gestation_of_interest): logger.info(key='maternal_complication', data={'person': person, 'type': 'gest_diab', 'timing': 'antenatal'}) + self.mnh_outcome_counter['gest_diab'] += 1 + def apply_risk_of_hypertensive_disorders(self, gestation_of_interest): """ @@ -1127,6 +1139,7 @@ def apply_risk_of_hypertensive_disorders(self, gestation_of_interest): logger.info(key='maternal_complication', data={'person': person, 'type': 'mild_pre_eclamp', 'timing': 'antenatal'}) + self.mnh_outcome_counter['mild_pre_eclamp'] += 1 # -------------------------------- RISK OF GESTATIONAL HYPERTENSION -------------------------------------- # For women who dont develop pre-eclampsia during this month, we apply a risk of gestational hypertension @@ -1141,6 +1154,7 @@ def apply_risk_of_hypertensive_disorders(self, gestation_of_interest): logger.info(key='maternal_complication', data={'person': person, 'type': 'mild_gest_htn', 'timing': 'antenatal'}) + self.mnh_outcome_counter['mild_gest_htn'] += 1 def apply_risk_of_progression_of_hypertension(self, gestation_of_interest): """ @@ -1198,6 +1212,8 @@ def log_new_progressed_cases(disease): 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 @@ -1273,6 +1289,7 @@ def apply_risk_of_placental_abruption(self, gestation_of_interest): logger.info(key='maternal_complication', data={'person': person, 'type': 'placental_abruption', 'timing': 'antenatal'}) + self.mnh_outcome_counter['placental_abruption'] += 1 def apply_risk_of_antepartum_haemorrhage(self, gestation_of_interest): """ @@ -1314,6 +1331,7 @@ def apply_risk_of_antepartum_haemorrhage(self, gestation_of_interest): logger.info(key='maternal_complication', data={'person': person, 'type': 'severe_antepartum_haemorrhage', 'timing': 'antenatal'}) + self.mnh_outcome_counter['severe_antepartum_haemorrhage'] += 1 non_severe_women = (df.loc[antepartum_haemorrhage.loc[antepartum_haemorrhage].index, 'ps_antepartum_haemorrhage'] != 'severe') @@ -1326,6 +1344,8 @@ def apply_risk_of_antepartum_haemorrhage(self, gestation_of_interest): logger.info(key='maternal_complication', data={'person': person, 'type': 'mild_mod_antepartum_haemorrhage', 'timing': 'antenatal'}) + self.mnh_outcome_counter['mild_mod_antepartum_haemorrhage'] += 1 + def apply_risk_of_premature_rupture_of_membranes_and_chorioamnionitis(self, gestation_of_interest): """ @@ -1354,6 +1374,7 @@ def apply_risk_of_premature_rupture_of_membranes_and_chorioamnionitis(self, gest logger.info(key='maternal_complication', data={'person': person, 'type': 'PROM', 'timing': 'antenatal'}) + self.mnh_outcome_counter['PROM'] += 1 # 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'], @@ -1369,6 +1390,7 @@ def apply_risk_of_premature_rupture_of_membranes_and_chorioamnionitis(self, gest 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): """ @@ -1433,6 +1455,7 @@ def update_variables_post_still_birth_for_data_frame(self, women): 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) @@ -1455,6 +1478,7 @@ def update_variables_post_still_birth_for_individual(self, individual_id): 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) @@ -1768,6 +1792,7 @@ def apply(self, population): 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)))) @@ -1787,6 +1812,7 @@ def apply(self, population): logger.info(key='maternal_complication', data={'person': person, 'type': 'multiple_pregnancy', 'timing': 'antenatal'}) + self.module.mnh_outcome_counter['multiple_pregnancy'] += 1 # -----------------------------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 @@ -1801,6 +1827,7 @@ def apply(self, population): logger.info(key='maternal_complication', data={'person': person, 'type': 'placenta_praevia', 'timing': 'antenatal'}) + self.module.mnh_outcome_counter['placenta_praevia'] += 1 # ------------------------- APPLYING RISK OF SYPHILIS INFECTION DURING PREGNANCY --------------------------- # Finally apply risk that syphilis will develop during pregnancy @@ -1987,6 +2014,7 @@ def apply(self, individual_id): 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' @@ -2097,7 +2125,7 @@ def apply(self, individual_id): 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 @@ -2236,36 +2264,66 @@ def __init__(self, module): def apply(self, population): df = self.sim.population.props - 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}) + # Complication incidence + # Denominators + yrly_live_births = len(df.index[(df.date_of_birth.year == self.sim.date.year)]) + yrly_pregnancies = len(df.index[(df.date_of_last_pregnancy.year == self.sim.date.year)]) + yrly_comp_pregnancies = [] + + # # Lets only do purely antenatal stuff here, purely labour stuff in labour and everything else in postnatal/newborn + # + # ectopic_incidence = (self.module.mnh_outcome_counter[''] / yrly_pregnancies) * 1000 + # abortion_incidence = + # miscarriage_incidence = + + + + + # logger.info(key='an_comp_incidence', + # 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} + # + # + # + # + # + # + # 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}) + + self.module.mnh_outcome_counter = {k:0 for k in self.module.outcome_list} From 87d6f1f98e91ed1309564c33f51b906599d7ec3c Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 7 Oct 2024 14:02:04 +0100 Subject: [PATCH 15/38] created mnh_outcome_logger to eventually replace logging for complication incidence --- src/tlo/methods/pregnancy_helper_functions.py | 98 +++++-------------- src/tlo/methods/pregnancy_supervisor.py | 8 +- 2 files changed, 29 insertions(+), 77 deletions(-) diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index 4866d78ce7..0eb75480ab 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -9,79 +9,31 @@ from tlo import logging def generate_mnh_outcome_counter(): - outcome_list = ['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', - 'gest_diab', - 'clinical_chorioamnionitis', - 'antenatal_stillbirth', - 'ectopic_unruptured', - 'multiple_pregnancy', - 'placenta_praevia', - 'ectopic_ruptured', - 'syphilis', - 'placental_abruption', - 'severe_antepartum_haemorrhage', - 'mild_mod_antepartum_haemorrhage', - 'PROM' - 'obstruction_cpd', - 'obstruction_malpos_malpres', - 'obstruction_other', - 'obstructed_labour', - 'sepsis_intrapartum', - 'uterine_rupture', 'early_preterm_labour', 'late_preterm_labour', 'post_term_labour', - 'intrapartum_stillbirth', - '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', - 'gest_diab', - 'mild_pre_eclamp', - 'mild_gest_htn' - 'severe_pre_eclamp', - 'eclampsia', - 'severe_gest_htn', - 'placental_abruption', - 'severe_antepartum_haemorrhage', - 'mild_mod_antepartum_haemorrhage', - 'PROM' - 'clinical_chorioamnionitis', - 'antenatal_stillbirth', - 'ectopic_unruptured', - 'multiple_pregnancy', - 'placenta_praevia', - 'ectopic_ruptured', - 'syphilis', - 'obstruction_cpd', - 'obstruction_malpos_malpres', - 'obstruction_other', - 'obstructed_labour', - 'sepsis_intrapartum', - 'uterine_rupture', 'early_preterm_labour', 'late_preterm_labour', 'post_term_labour', - 'intrapartum_stillbirth', 'vesicovaginal_fistula', 'rectovaginal_fistula', - 'secondary_postpartum_haemorrhage', 'pph_uterine_atony', 'pph_retained_placenta', 'pph_other', - 'primary_postpartum_haemorrhage', 'sepsis_endometritis', 'sepsis_urinary_tract', - 'sepsis_skin_soft_tissue', 'sepsis_postnatal', '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', - ] + outcome_list = [ # early/abortive outcomes + 'ectopic_unruptured', 'ectopic_ruptured','multiple_pregnancy', '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 + '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', + + # 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'] mnh_outcome_counter = {k: 0 for k in outcome_list} diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index e82eb71fad..8a13d62164 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -2266,9 +2266,9 @@ def apply(self, population): # Complication incidence # Denominators - yrly_live_births = len(df.index[(df.date_of_birth.year == self.sim.date.year)]) - yrly_pregnancies = len(df.index[(df.date_of_last_pregnancy.year == self.sim.date.year)]) - yrly_comp_pregnancies = [] + # yrly_live_births = len(df.index[(df.date_of_birth.year == self.sim.date.year)]) + # yrly_pregnancies = len(df.index[(df.date_of_last_pregnancy.year == self.sim.date.year)]) + # yrly_comp_pregnancies = [] # # Lets only do purely antenatal stuff here, purely labour stuff in labour and everything else in postnatal/newborn # @@ -2326,4 +2326,4 @@ def apply(self, population): # 'hysterectomy': yearly_prev_hysterectomy, # 'parity': parity_list}) - self.module.mnh_outcome_counter = {k:0 for k in self.module.outcome_list} + # self.module.mnh_outcome_counter = {k:0 for k in self.module.outcome_list} From cdd98420615dba156a37e36ef484058821008963 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 7 Oct 2024 16:16:05 +0100 Subject: [PATCH 16/38] added first logging to pregnancy supervisor --- src/tlo/methods/pregnancy_helper_functions.py | 3 +- src/tlo/methods/pregnancy_supervisor.py | 71 ++++++++++++++++--- 2 files changed, 63 insertions(+), 11 deletions(-) diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index 0eb75480ab..93607873df 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -37,7 +37,8 @@ def generate_mnh_outcome_counter(): mnh_outcome_counter = {k: 0 for k in outcome_list} - return mnh_outcome_counter + return {'counter': mnh_outcome_counter, + 'outcomes': outcome_list} def get_list_of_items(self, item_list): """ diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 8a13d62164..34f52e6271 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -63,7 +63,8 @@ def __init__(self, name=None, resourcefilepath=None): self.abortion_complications = None # Finally we create a dictionary to capture the frequency of key outcomes for logging - self.mnh_outcome_counter = pregnancy_helper_functions.generate_mnh_outcome_counter() + mnh_oc = pregnancy_helper_functions.generate_mnh_outcome_counter() + self.mnh_outcome_counter = mnh_oc['counter'] INIT_DEPENDENCIES = {'Demography'} @@ -2263,18 +2264,66 @@ def __init__(self, module): def apply(self, population): df = self.sim.population.props + counter = self.module.mnh_outcome_counter # Complication incidence # Denominators - # yrly_live_births = len(df.index[(df.date_of_birth.year == self.sim.date.year)]) - # yrly_pregnancies = len(df.index[(df.date_of_last_pregnancy.year == self.sim.date.year)]) - # yrly_comp_pregnancies = [] + yrly_live_births = len(df[(df['date_of_birth'].dt.year == self.sim.date.year - 1) & (df['mother_id'] >= 0)]) + yrly_pregnancies =len(df[df['date_of_last_pregnancy'].dt.year == self.sim.date.year - 1]) - # # Lets only do purely antenatal stuff here, purely labour stuff in labour and everything else in postnatal/newborn - # - # ectopic_incidence = (self.module.mnh_outcome_counter[''] / yrly_pregnancies) * 1000 - # abortion_incidence = - # miscarriage_incidence = + yrly_comp_pregnancies = (counter['ectopic_unruptured'] + counter['spontaneous_abortion'] + + counter['induced_abortion'] + counter['antenatal_stillbirth'] + + counter['intrapartum_stillbirth'] + yrly_live_births) + + yrly_total_births = yrly_live_births + counter['antenatal_stillbirth'] + counter['intrapartum_stillbirth'] + + logger.info(key='yrl_counter_dict', + data=counter) + + # MATERNAL COMPLICATIONS + logger.info(key='mat_comp_incidence', + data= {k:(counter[k]/denom) * 1000 for k, denom in + zip(['ectopic_unruptured', 'induced_abortion', 'spontaneous_abortion', + 'placenta_praevia', 'gest_diab', 'syphilis'], + [yrly_pregnancies, yrly_comp_pregnancies, yrly_comp_pregnancies, yrly_pregnancies, + yrly_comp_pregnancies, yrly_comp_pregnancies,])}) + + logger.info(key='mat_comp_incidence', + data={k: (counter[k] / yrly_live_births) * 1000 for k in + ['obstructed_labour', 'uterine_rupture', 'sepsis_intrapartum', + 'mild_mod_antepartum_haemorrhage', 'severe_antepartum_haemorrhage', + 'mild_pre_eclamp', 'mild_gest_htn', 'mild_gest_htn', 'severe_pre_eclamp', + 'severe_gest_htn', 'eclampsia', 'sepsis_postnatal', 'primary_postpartum_haemorrhage', + 'secondary_postpartum_haemorrhage', 'vesicovaginal_fistula', 'rectovaginal_fistula']}) + + logger.info(key='mat_comp_incidence', + data={'antepartum_haemorrhage': ((counter['mild_mod_antepartum_haemorrhage'] + + counter['severe_antepartum_haemorrhage']) / + yrly_live_births) * 1000}) + + logger.info(key='mat_comp_incidence', + data={k: (counter[k] / yrly_live_births) * 100 for k in + ['early_preterm_labour', 'late_preterm_labour', 'post_term_labour']}) + + # NEWBORN COMPLICATIONS + logger.info(key='nb_incidence', + data={k: (counter[k] / yrly_live_births) * 1000 for k in + ['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']}) + + + neonatal_deaths = len(df[(df['date_of_death'].dt.year == self.sim.date.year - 1) & (df['age_days'] <= 28)]) + direct_maternal_deaths = [] + + # DIRECT MATERNAL DEATHS, NEWBORN DEATHS AND STILLBIRTH + logger.info(key='deaths_and_stillbirths', + data={'antenatal_stillbirth': (counter['antenatal_stillbirth'] / yrly_total_births) * 1000, + 'intrapartum_stillbirth': (counter['intrapartum_stillbirth'] / yrly_total_births) * 1000, + 'neonatal_deaths': neonatal_deaths, + 'nmr' : (neonatal_deaths/yrly_live_births) * 1000, + 'direct_maternal_deaths': []}) @@ -2326,4 +2375,6 @@ def apply(self, population): # 'hysterectomy': yearly_prev_hysterectomy, # 'parity': parity_list}) - # self.module.mnh_outcome_counter = {k:0 for k in self.module.outcome_list} + 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} From b9a193dafd32a8544eb8cc463175433cbccdab86 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 8 Oct 2024 10:23:36 +0100 Subject: [PATCH 17/38] finalising logging event --- src/tlo/methods/labour.py | 4 + src/tlo/methods/newborn_outcomes.py | 1 + src/tlo/methods/postnatal_supervisor.py | 8 + src/tlo/methods/pregnancy_helper_functions.py | 11 +- src/tlo/methods/pregnancy_supervisor.py | 164 ++++++++---------- 5 files changed, 91 insertions(+), 97 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 02fcd65630..21bc602d01 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1529,9 +1529,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: @@ -2700,6 +2703,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']) diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index b0e8d461bd..4019544bd4 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -1030,6 +1030,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 diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 428be94aaa..ef81b3b8a5 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -838,6 +838,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] @@ -950,6 +951,13 @@ 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: 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': + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[ + f'pn_anaemia_{df.at[person, "pn_anaemia_following_pregnancy"]}'] += 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']}) diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index 93607873df..ad3237328f 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -10,13 +10,14 @@ def generate_mnh_outcome_counter(): outcome_list = [ # early/abortive outcomes - 'ectopic_unruptured', 'ectopic_ruptured','multiple_pregnancy', 'placenta_praevia', + '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', @@ -27,13 +28,17 @@ def generate_mnh_outcome_counter(): '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', + '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'] + 'macrosomia', 'small_for_gestational_age', 'early_onset_sepsis', 'late_onset_sepsis', + + 'direct_mat_death', 'six_week_survivors' + ] mnh_outcome_counter = {k: 0 for k in outcome_list} diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 34f52e6271..671a6af07a 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -658,6 +658,9 @@ 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 + 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 + 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'], @@ -1609,6 +1612,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 @@ -2264,116 +2268,88 @@ def __init__(self, module): def apply(self, population): df = self.sim.population.props - counter = self.module.mnh_outcome_counter + c = self.module.mnh_outcome_counter - # Complication incidence - # Denominators - yrly_live_births = len(df[(df['date_of_birth'].dt.year == self.sim.date.year - 1) & (df['mother_id'] >= 0)]) - yrly_pregnancies =len(df[df['date_of_last_pregnancy'].dt.year == self.sim.date.year - 1]) + logger.info(key='yrly_counter_dict', data=c) - yrly_comp_pregnancies = (counter['ectopic_unruptured'] + counter['spontaneous_abortion'] + - counter['induced_abortion'] + counter['antenatal_stillbirth'] + - counter['intrapartum_stillbirth'] + yrly_live_births) + def rate (count, denom, multiplier): + return (count/denom) * multiplier - yrly_total_births = yrly_live_births + counter['antenatal_stillbirth'] + counter['intrapartum_stillbirth'] + # DENOMINATORS + live_births = len(df[(df['date_of_birth'].dt.year == self.sim.date.year - 1) & (df['mother_id'] >= 0)]) - logger.info(key='yrl_counter_dict', - data=counter) + pregnancies =len(df[df['date_of_last_pregnancy'].dt.year == self.sim.date.year - 1]) - # MATERNAL COMPLICATIONS - logger.info(key='mat_comp_incidence', - data= {k:(counter[k]/denom) * 1000 for k, denom in - zip(['ectopic_unruptured', 'induced_abortion', 'spontaneous_abortion', - 'placenta_praevia', 'gest_diab', 'syphilis'], - [yrly_pregnancies, yrly_comp_pregnancies, yrly_comp_pregnancies, yrly_pregnancies, - yrly_comp_pregnancies, yrly_comp_pregnancies,])}) + comp_pregnancies = (c['ectopic_unruptured'] + c['spontaneous_abortion'] + + c['induced_abortion'] + c['antenatal_stillbirth'] + + c['intrapartum_stillbirth'] + live_births) - logger.info(key='mat_comp_incidence', - data={k: (counter[k] / yrly_live_births) * 1000 for k in - ['obstructed_labour', 'uterine_rupture', 'sepsis_intrapartum', - 'mild_mod_antepartum_haemorrhage', 'severe_antepartum_haemorrhage', - 'mild_pre_eclamp', 'mild_gest_htn', 'mild_gest_htn', 'severe_pre_eclamp', - 'severe_gest_htn', 'eclampsia', 'sepsis_postnatal', 'primary_postpartum_haemorrhage', - 'secondary_postpartum_haemorrhage', 'vesicovaginal_fistula', 'rectovaginal_fistula']}) + deliveries = live_births - c['twin_birth'] - logger.info(key='mat_comp_incidence', - data={'antepartum_haemorrhage': ((counter['mild_mod_antepartum_haemorrhage'] + - counter['severe_antepartum_haemorrhage']) / - yrly_live_births) * 1000}) + total_births = live_births + c['antenatal_stillbirth'] + c['intrapartum_stillbirth'] + + # MATERNAL COMPLICATION INCIDENCE + 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_preterm_birth = c['early_preterm_labour'] + c['late_preterm_labour'] + 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={k: (counter[k] / yrly_live_births) * 100 for k in - ['early_preterm_labour', 'late_preterm_labour', 'post_term_labour']}) + 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_incidence', - data={k: (counter[k] / yrly_live_births) * 1000 for k in - ['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']}) - + 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 neonatal_deaths = len(df[(df['date_of_death'].dt.year == self.sim.date.year - 1) & (df['age_days'] <= 28)]) - direct_maternal_deaths = [] + stillbirths = c['antenatal_stillbirth'] + c['intrapartum_stillbirth'] - # DIRECT MATERNAL DEATHS, NEWBORN DEATHS AND STILLBIRTH logger.info(key='deaths_and_stillbirths', - data={'antenatal_stillbirth': (counter['antenatal_stillbirth'] / yrly_total_births) * 1000, - 'intrapartum_stillbirth': (counter['intrapartum_stillbirth'] / yrly_total_births) * 1000, + 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' : (neonatal_deaths/yrly_live_births) * 1000, - 'direct_maternal_deaths': []}) - - - - - # logger.info(key='an_comp_incidence', - # 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} - # - # - # - # - # - # - # 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}) + '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), + }) mnh_oc = pregnancy_helper_functions.generate_mnh_outcome_counter() outcome_list = mnh_oc['outcomes'] From ada05e089db947e31b5e85531bde429e839deb07 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 8 Oct 2024 12:58:21 +0100 Subject: [PATCH 18/38] add health system logging --- .../methods/care_of_women_during_pregnancy.py | 9 ++++++- src/tlo/methods/labour.py | 3 +++ src/tlo/methods/postnatal_supervisor.py | 18 +++++++++++-- src/tlo/methods/pregnancy_helper_functions.py | 9 ++++++- src/tlo/methods/pregnancy_supervisor.py | 25 +++++++++++++++++-- 5 files changed, 58 insertions(+), 6 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 69ce038299..b2df6c777b 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -484,8 +484,10 @@ 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 @@ -494,6 +496,11 @@ def further_on_birth_care_of_women_in_pregnancy(self, mother_id): else: ga_anc_one = 0.0 + 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 + 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} diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 21bc602d01..d84c41c1e0 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1060,6 +1060,9 @@ def further_on_birth_labour(self, mother_id): 'facility_type': str(mni[mother_id]['delivery_setting']), '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']: df.at[mother_id, 'la_parity'] += 1 # Only live births contribute to parity diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index ef81b3b8a5..5ba48a0e24 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -895,6 +895,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 @@ -950,13 +951,19 @@ 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': - self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[ - f'pn_anaemia_{df.at[person, "pn_anaemia_following_pregnancy"]}'] += 1 + 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'], @@ -977,6 +984,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']}) diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index ad3237328f..3353f86fc8 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -37,7 +37,13 @@ def generate_mnh_outcome_counter(): 'severe_enceph', 'respiratory_distress_syndrome', 'not_breathing_at_birth', 'low_birth_weight', 'macrosomia', 'small_for_gestational_age', 'early_onset_sepsis', 'late_onset_sepsis', - 'direct_mat_death', 'six_week_survivors' + # death outcomes + 'direct_mat_death', 'six_week_survivors', + + # 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} @@ -386,6 +392,7 @@ def calculate_risk_of_death_from_causes(self, risks): 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 + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[f'{cause_of_death}_death'] += 1 return cause_of_death else: # Return false if death will not occur diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 671a6af07a..520474222d 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -2348,8 +2348,29 @@ def rate (count, denom, multiplier): '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), - }) + 'direct_mmr': rate(c['direct_mat_death'], live_births, 100_000)}) + + 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, 1000), + 'n_pnc1+': rate(n_pnc1, total_births, 1000)}) + mnh_oc = pregnancy_helper_functions.generate_mnh_outcome_counter() outcome_list = mnh_oc['outcomes'] From bfac2451d4822ce885f8d526ace23182374196a2 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 8 Oct 2024 13:27:39 +0100 Subject: [PATCH 19/38] old logging removed --- .../methods/care_of_women_during_pregnancy.py | 16 ++- src/tlo/methods/labour.py | 59 ++-------- src/tlo/methods/newborn_outcomes.py | 24 ----- src/tlo/methods/postnatal_supervisor.py | 53 +-------- src/tlo/methods/pregnancy_helper_functions.py | 1 + src/tlo/methods/pregnancy_supervisor.py | 101 ++---------------- 6 files changed, 27 insertions(+), 227 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index b2df6c777b..d1d52f076a 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -490,22 +490,18 @@ def further_on_birth_care_of_women_in_pregnancy(self, mother_id): 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 'ga_anc_one' in mni[mother_id]: - ga_anc_one = float(mni[mother_id]['ga_anc_one']) - else: - ga_anc_one = 0.0 - 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 - 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} + # 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 - 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): diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index d84c41c1e0..3d97aa23dd 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1056,9 +1056,7 @@ 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 @@ -1285,10 +1283,6 @@ 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': @@ -1298,11 +1292,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 + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['placental_abruption'] += 1 elif complication == 'antepartum_haem': random_choice = self.rng.choice(['mild_moderate', 'severe'], @@ -1312,18 +1303,13 @@ 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 + 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': @@ -1331,18 +1317,14 @@ def set_intrapartum_complications(self, individual_id, complication): 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): @@ -1396,9 +1378,6 @@ 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 @@ -2439,10 +2418,6 @@ 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' @@ -2451,19 +2426,11 @@ 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'] @@ -2631,9 +2598,6 @@ 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 @@ -2750,8 +2714,6 @@ 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 @@ -2840,15 +2802,9 @@ 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') @@ -3002,9 +2958,6 @@ 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 ========================== diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 4019544bd4..e8f32ed7af 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -523,32 +523,22 @@ 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): @@ -563,9 +553,6 @@ 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): @@ -598,8 +585,6 @@ 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 @@ -623,9 +608,6 @@ 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): @@ -646,9 +628,6 @@ 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): @@ -1167,16 +1146,13 @@ 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 diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 5ba48a0e24..34c31fca9e 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -354,10 +354,6 @@ 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 @@ -501,9 +497,6 @@ 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 ---------------------------------------------------------- @@ -516,10 +509,6 @@ 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 -------------------------------------------------- @@ -597,9 +586,6 @@ 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': @@ -641,12 +627,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'] += 1 + 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( @@ -655,12 +637,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'] += 1 + 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 @@ -750,9 +728,6 @@ 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): @@ -778,9 +753,6 @@ 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 @@ -1057,9 +1029,6 @@ 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 @@ -1076,10 +1045,6 @@ 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 ------------------------------------------ @@ -1146,9 +1111,6 @@ 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': @@ -1170,9 +1132,6 @@ 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: @@ -1180,10 +1139,6 @@ def log_new_progressed_cases(disease): 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 ================================================== diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index 3353f86fc8..446ad0a433 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -8,6 +8,7 @@ from tlo import logging + def generate_mnh_outcome_counter(): outcome_list = [ # early/abortive outcomes 'ectopic_unruptured', 'ectopic_ruptured','multiple_pregnancy', 'twin_birth', 'placenta_praevia', diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 520474222d..ff3295a5c2 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -661,11 +661,6 @@ def further_on_birth_pregnancy_supervisor(self, mother_id): 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 - 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']}) - # We currently assume that hyperglycemia due to gestational diabetes resolves following birth if df.at[mother_id, 'ps_gest_diab'] != 'none': df.at[mother_id, 'ps_gest_diab'] = 'none' @@ -974,9 +969,6 @@ 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 @@ -1004,10 +996,6 @@ 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}') @@ -1026,34 +1014,22 @@ 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 @@ -1113,11 +1089,7 @@ 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'] += 1 + self.mnh_outcome_counter['gest_diab'] += len(gest_diab.loc[gest_diab].index) def apply_risk_of_hypertensive_disorders(self, gestation_of_interest): @@ -1139,11 +1111,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'] += 1 + 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 @@ -1154,11 +1122,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'] += 1 + self.mnh_outcome_counter['mild_gest_htn'] += len(gest_hypertension.loc[gest_hypertension].inde) def apply_risk_of_progression_of_hypertension(self, gestation_of_interest): """ @@ -1213,9 +1177,6 @@ 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': @@ -1289,11 +1250,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'] += 1 + self.mnh_outcome_counter['placental_abruption'] += len(placenta_abruption.loc[placenta_abruption].index) def apply_risk_of_antepartum_haemorrhage(self, gestation_of_interest): """ @@ -1331,11 +1288,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'] += 1 + 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') @@ -1344,12 +1297,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'] += 1 - + 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): """ @@ -1373,12 +1321,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'] += 1 + 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'], @@ -1391,9 +1334,6 @@ 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): @@ -1458,7 +1398,6 @@ 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 @@ -1481,7 +1420,6 @@ 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 @@ -1794,9 +1732,6 @@ 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), @@ -1812,12 +1747,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'] += 1 + 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 @@ -1827,12 +1757,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'] += 1 + 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 @@ -2016,9 +1941,6 @@ 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 @@ -2127,9 +2049,7 @@ 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): @@ -2371,7 +2291,6 @@ def rate (count, denom, multiplier): 'm_pnc1+': rate(m_pnc1, total_births, 1000), 'n_pnc1+': rate(n_pnc1, total_births, 1000)}) - 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} From 905091b6ed1ad32f25bf9e454b3af116821cb6c2 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 8 Oct 2024 13:35:41 +0100 Subject: [PATCH 20/38] fix --- src/tlo/methods/pregnancy_supervisor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index ff3295a5c2..20c91fe4ef 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -1122,7 +1122,7 @@ def apply_risk_of_hypertensive_disorders(self, gestation_of_interest): df.loc[gest_hypertension.loc[gest_hypertension].index, 'ps_htn_disorders'] = 'gest_htn' - self.mnh_outcome_counter['mild_gest_htn'] += len(gest_hypertension.loc[gest_hypertension].inde) + 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): """ From 5a04b444bb38c1c39b46ca462c6a9b6333e7d195 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 8 Oct 2024 14:06:34 +0100 Subject: [PATCH 21/38] fix --- src/tlo/methods/pregnancy_supervisor.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 20c91fe4ef..4185bd935a 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -2190,11 +2190,6 @@ def apply(self, population): df = self.sim.population.props c = self.module.mnh_outcome_counter - logger.info(key='yrly_counter_dict', data=c) - - def rate (count, denom, multiplier): - return (count/denom) * multiplier - # DENOMINATORS live_births = len(df[(df['date_of_birth'].dt.year == self.sim.date.year - 1) & (df['mother_id'] >= 0)]) @@ -2208,7 +2203,15 @@ def rate (count, denom, multiplier): total_births = live_births + c['antenatal_stillbirth'] + c['intrapartum_stillbirth'] + if (live_births == 0) or (pregnancies == 0): + return + # MATERNAL COMPLICATION INCIDENCE + logger.info(key='yearly_counter_dict', data=c) + + 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_preterm_birth = c['early_preterm_labour'] + c['late_preterm_labour'] From c62d5df215023617e8acc4adbf2951f61313c196 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 8 Oct 2024 14:26:45 +0100 Subject: [PATCH 22/38] fix --- src/tlo/methods/pregnancy_supervisor.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 4185bd935a..408b5c7847 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -2192,19 +2192,17 @@ def apply(self, population): # DENOMINATORS 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'] - if (live_births == 0) or (pregnancies == 0): - return + for denom in [live_births, pregnancies, comp_pregnancies, deliveries, total_births, total_preterm_birth]: + if denom == 0: + return # MATERNAL COMPLICATION INCIDENCE logger.info(key='yearly_counter_dict', data=c) @@ -2214,7 +2212,6 @@ def rate (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_preterm_birth = c['early_preterm_labour'] + c['late_preterm_labour'] 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'] From d8423e1b93130e6af2935306e11f09ab5e6cdf56 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 8 Oct 2024 14:32:11 +0100 Subject: [PATCH 23/38] comments for clarity --- src/tlo/methods/pregnancy_supervisor.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 408b5c7847..4f2e05dfcb 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -2191,6 +2191,7 @@ def apply(self, population): 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'] + @@ -2205,8 +2206,10 @@ def apply(self, population): return # MATERNAL COMPLICATION INCIDENCE - logger.info(key='yearly_counter_dict', data=c) + # 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 @@ -2257,6 +2260,7 @@ def rate (count, denom, multiplier): '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'] @@ -2270,6 +2274,7 @@ def rate (count, denom, multiplier): 'direct_maternal_deaths': c['direct_mat_death'], 'direct_mmr': rate(c['direct_mat_death'], live_births, 100_000)}) + # 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+'] @@ -2291,6 +2296,7 @@ def rate (count, denom, multiplier): 'm_pnc1+': rate(m_pnc1, total_births, 1000), 'n_pnc1+': rate(n_pnc1, total_births, 1000)}) + # 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} From cd3bd347a3b21f20aa3fa9499a804118d8df0c47 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 8 Oct 2024 14:58:32 +0100 Subject: [PATCH 24/38] improved commenting --- .../cohort_interventions_scenario.py | 2 +- src/tlo/methods/mnh_cohort_module.py | 61 ++++--------------- 2 files changed, 14 insertions(+), 49 deletions(-) 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 index 30b5ebfc09..11cac4deb7 100644 --- a/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py +++ b/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py @@ -9,7 +9,7 @@ def __init__(self): super().__init__() self.seed = 537184 self.start_date = Date(2024, 1, 1) - self.end_date = Date(2025, 1, 1) + self.end_date = Date(2025, 1, 2) self.pop_size = 5000 self.number_of_draws = 1 self.runs_per_draw = 10 diff --git a/src/tlo/methods/mnh_cohort_module.py b/src/tlo/methods/mnh_cohort_module.py index f5e19c6767..582855df5d 100644 --- a/src/tlo/methods/mnh_cohort_module.py +++ b/src/tlo/methods/mnh_cohort_module.py @@ -11,47 +11,30 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) -# --------------------------------------------------------------------------------------------------------- -# MODULE DEFINITIONS -# --------------------------------------------------------------------------------------------------------- - -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. """ - # INIT_DEPENDENCIES = {'Demography'} - # - # OPTIONAL_INIT_DEPENDENCIES = {''} - # - # ADDITIONAL_DEPENDENCIES = {''} - - # Declare Metadata (this is for a typical 'Disease Module') 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): - # NB. Parameters passed to the module can be inserted in the __init__ definition. - super().__init__(name) self.resourcefilepath = resourcefilepath def read_parameters(self, data_folder): - """Read parameter values from file, if required. - To access files use: Path(self.resourcefilepath) / file_name - """ pass def initialise_population(self, population): @@ -73,23 +56,28 @@ def initialise_population(self, population): # 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 @@ -109,13 +97,12 @@ def initialise_simulation(self, sim): # Clear HSI queue for events scheduled during initialisation sim.modules['HealthSystem'].HSI_EVENT_QUEUE.clear() - # Clear HSI queue for events scheduled during initialisation + # 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 + # 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 @@ -124,32 +111,10 @@ def initialise_simulation(self, sim): self.sim.modules['Labour'].set_date_of_labour(person) def on_birth(self, mother_id, child_id): - """Initialise our properties for a newborn individual. - - This is called by the simulation whenever a new person is born. - - :param mother_id: the mother for this child - :param child_id: the new child - """ pass def report_daly_values(self): - """ - This must send back a pd.Series or pd.DataFrame that reports on the average daly-weights that have been - experienced by persons in the previous month. Only rows for alive-persons must be returned. - If multiple causes in CAUSES_OF_DISABILITY are defined, a pd.DataFrame must be returned with a column - corresponding to each cause (but if only one cause in CAUSES_OF_DISABILITY is defined, the pd.Series does not - need to be given a specific name). - - To return a value of 0.0 (fully health) for everyone, use: - df = self.sim.population.props - return pd.Series(index=df.index[df.is_alive],data=0.0) - """ pass def on_hsi_alert(self, person_id, treatment_id): - """ - This is called whenever there is an HSI event commissioned by one of the other disease modules. - """ - pass From a6b12fe5d200c736043150d81a3097835cc18763 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 8 Oct 2024 15:33:09 +0100 Subject: [PATCH 25/38] removed counting which wasnt needed --- src/tlo/methods/pregnancy_helper_functions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index 446ad0a433..3ae5b5b264 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -393,7 +393,6 @@ def calculate_risk_of_death_from_causes(self, risks): 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 - self.sim.modules['PregnancySupervisor'].mnh_outcome_counter[f'{cause_of_death}_death'] += 1 return cause_of_death else: # Return false if death will not occur From 5ca2314b4eb04cb14909fe835ab4e98f87688668 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 9 Oct 2024 10:30:13 +0100 Subject: [PATCH 26/38] fixes to pregnancy_supervisor test --- tests/test_pregnancy_supervisor.py | 33 +++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 10 deletions(-) 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) From ba22a9904bcc1a389225098d903d5027e2879937 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 9 Oct 2024 14:04:31 +0100 Subject: [PATCH 27/38] fix logic in HIV causing crashes --- src/tlo/methods/hiv.py | 2 +- src/tlo/methods/labour.py | 2 +- tests/test_mnh_cohort.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) 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 debe2b7e83..28c1684db0 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -875,7 +875,7 @@ def initialise_simulation(self, sim): 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 if self.sim.date.year <= self.current_parameters['analysis_year']: diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py index df9bc58e15..c73f8d7588 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -31,7 +31,7 @@ def test_run_sim_with_mnh_cohort(tmpdir, seed): register_modules(sim) sim.make_initial_population(n=2500) - sim.simulate(end_date=Date(2025, 1, 1)) + sim.simulate(end_date=Date(2025, 1, 2)) output= parse_log_file(sim.log_filepath) live_births = len(output['tlo.methods.demography']['on_birth']) From 011a0017b427f2342d10c76439ab0c785625cde5 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 10 Oct 2024 09:14:46 +0100 Subject: [PATCH 28/38] updates to tests and new dummy analysis script --- .../dummy_cohort_azure_calib.py | 28 ++++++ tests/test_mnh_cohort.py | 90 +++++++++++-------- 2 files changed, 80 insertions(+), 38 deletions(-) create mode 100644 src/scripts/maternal_perinatal_analyses/cohort_analysis/dummy_cohort_azure_calib.py 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..257c2f00e8 --- /dev/null +++ b/src/scripts/maternal_perinatal_analyses/cohort_analysis/dummy_cohort_azure_calib.py @@ -0,0 +1,28 @@ +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' + +results_folder = get_scenario_outputs(scenario_filename, outputspath)[-1] + +def get_data_frames(key): + 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 = {k:get_data_frames(k) for k in ['mat_comp_incidence', 'nb_comp_incidence', 'deaths_and_stillbirths', + 'service_coverage']} diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py index c73f8d7588..184ef4b947 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -1,4 +1,7 @@ import os + +import pandas as pd + from pathlib import Path from tlo import Date, Simulation, logging @@ -21,51 +24,62 @@ def register_modules(sim): 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)) + mnh_cohort_module.MaternalNewbornHealthCohort(resourcefilepath=resourcefilepath)) - 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 +# 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}') - # 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]}') +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}) - maternal_dalys = output['tlo.methods.healthburden']['dalys_stacked']['Maternal Disorders'].sum() - print(f'The maternal DALYs for this simulation (unscaled) are {maternal_dalys}') + 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() - # df = sim.population.props # orig = sim.population.new_row # assert (df.dtypes == orig.dtypes).all() From 09a216f6b058b23d51cc20ebd3b5aa8618e891a3 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 10 Oct 2024 09:31:32 +0100 Subject: [PATCH 29/38] fix denom error --- src/tlo/methods/pregnancy_supervisor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 4f2e05dfcb..648cde5a59 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -2293,8 +2293,8 @@ def rate (count, denom, multiplier): '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, 1000), - 'n_pnc1+': rate(n_pnc1, total_births, 1000)}) + '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() From 03161e0b15306d4302db589b36cd49bd77376495 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 10 Oct 2024 09:40:50 +0100 Subject: [PATCH 30/38] fix indentation error --- src/tlo/methods/postnatal_supervisor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 34c31fca9e..c8cbbcc748 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -1139,7 +1139,7 @@ def log_new_progressed_cases(disease): individual_id]])[individual_id] if risk_gh_after_pregnancy > self.module.rng.random_sample(): df.at[individual_id, 'pn_htn_disorders'] = 'gest_htn' - self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['mild_gest_htn'] += 1 + self.sim.modules['PregnancySupervisor'].mnh_outcome_counter['mild_gest_htn'] += 1 # ====================================== POSTNATAL CHECK ================================================== # Import the HSI which represents postnatal care From fd239bcd789908861ec15934ab8a3adabcd3fe77 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 10 Oct 2024 13:40:14 +0100 Subject: [PATCH 31/38] fix indentation error --- .../dummy_cohort_azure_calib.py | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) 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 index 257c2f00e8..621bb9516f 100644 --- 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 @@ -26,3 +26,25 @@ def sort_df(_df): results = {k:get_data_frames(k) for k in ['mat_comp_incidence', 'nb_comp_incidence', 'deaths_and_stillbirths', 'service_coverage']} + + +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() From ae57c4031d9d07258bfecf589891cb531bc2b3ff Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 10 Oct 2024 14:19:18 +0100 Subject: [PATCH 32/38] revert comit --- .../cohort_analysis/cohort_interventions_scenario.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 index 11cac4deb7..5a64abf546 100644 --- a/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py +++ b/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py @@ -34,7 +34,8 @@ def log_configuration(self): } def modules(self): - return [*fullmodel(resourcefilepath=self.resources), + 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): From de3ee93e0065c29c570da7b625e436332eaef1d4 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 10 Oct 2024 14:24:21 +0100 Subject: [PATCH 33/38] added missing logging --- src/tlo/methods/labour.py | 16 ++++------------ src/tlo/methods/postnatal_supervisor.py | 10 ---------- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 3d97aa23dd..3692c691a8 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1446,9 +1446,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': @@ -1461,9 +1459,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': @@ -1471,9 +1467,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': @@ -1481,9 +1475,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): """ diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index c8cbbcc748..183877f4ed 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -525,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 @@ -1062,11 +1057,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 From 7c3bf259250ddb9d3f39cba31d97222fc3cbe782 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 14 Oct 2024 16:31:31 +0100 Subject: [PATCH 34/38] work on cohort --- .../ResourceFile_PregnancySupervisor.xlsx | 4 +- .../methods/care_of_women_during_pregnancy.py | 588 +++++++++++------- src/tlo/methods/pregnancy_helper_functions.py | 84 +++ src/tlo/methods/pregnancy_supervisor.py | 14 +- tests/test_mnh_cohort.py | 74 +-- 5 files changed, 508 insertions(+), 256 deletions(-) diff --git a/resources/ResourceFile_PregnancySupervisor.xlsx b/resources/ResourceFile_PregnancySupervisor.xlsx index 3ee3406d7a..b7b42c445d 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:f03864369f37f0a3d03deffe3b60f1f029172540fd692b1ece19b693979bab95 +size 23759 diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index d1d52f076a..44712b4ea0 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 @@ -728,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: @@ -798,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) - if avail: + # avail = pregnancy_helper_functions.return_cons_avail( + # self, hsi_event, cons=updated_cons, opt_cons=None) + # + # 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 @@ -835,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'}) @@ -901,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'}) @@ -923,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): @@ -996,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): """ @@ -1075,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 @@ -1247,24 +1318,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 @@ -1287,11 +1368,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): @@ -1305,16 +1391,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 @@ -1338,22 +1431,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): """ @@ -1363,20 +1466,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): """ @@ -2522,11 +2634,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 @@ -2546,10 +2664,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: - 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 gdm_insulin_delivered: df.at[person_id, 'ac_gest_diab_on_treatment'] = 'insulin' df.at[person_id, 'ps_gest_diab'] = 'controlled' @@ -2584,65 +2708,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']) - - # 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'}) + 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'] - # 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 - - 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], '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']) - 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 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']) - if cons_for_haemorrhage and cons_for_shock and (baseline_cons or sf_check): - df.at[person_id, 'ac_received_post_abortion_care'] = True + 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'}) - 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) @@ -2674,21 +2824,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/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index 3ae5b5b264..dd3dbce209 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -63,6 +63,90 @@ def get_list_of_items(self, item_list): return codes +def check_int_deliverable(self, int_name, hsi_event, q_param, cons, 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: add to newborn interventions + 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 + int_avail_df = self.sim.modules['PregnancySupervisor'].current_parameters['intervention_availability'] + + # 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 self.sim.modules['PregnancySupervisor'].current_parameters['ps_analysis_in_progress']: + + # If so, we determine if this intervention will be delivered given the set probability of delivery. + can_int_run_analysis = self.rng.random_sample() < int_avail_df.at[int_name, '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 all([self.rng.random_sample() < value for value in q_param]) or (q_param is None): + quality = True + + if equipment is not None: + hsi_event.add_equipment({equipment}) + + if ((hsi_event.get_consumables(item_codes=cons, optional_item_codes=opt_cons if not None else [])) or + (cons is None)): + consumables = True + + if (self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run=dx_test, hsi_event=hsi_event) + or (dx_test is None)): + test = True + + if quality and consumables and test: + return True + + else: + return False + def return_cons_avail(self, hsi_event, cons, opt_cons): """ diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index 2868dd425d..34ac27a916 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -389,6 +389,10 @@ 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, ''), + } PROPERTIES = { @@ -437,10 +441,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 diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py index 184ef4b947..efb37029bb 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -26,43 +26,43 @@ def register_modules(sim): 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_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=2000) + 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): From 53ed3bbd8ce6fd68e00d2775d79b0c97369652d4 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 15 Oct 2024 12:45:50 +0100 Subject: [PATCH 35/38] work on cohort --- ...urceFile_LabourSkilledBirthAttendance.xlsx | 4 +- .../ResourceFile_PregnancySupervisor.xlsx | 4 +- .../methods/care_of_women_during_pregnancy.py | 24 +- src/tlo/methods/labour.py | 526 +++++++++++------- src/tlo/methods/newborn_outcomes.py | 61 +- src/tlo/methods/pregnancy_helper_functions.py | 21 +- tests/test_mnh_cohort.py | 2 +- 7 files changed, 399 insertions(+), 243 deletions(-) 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_PregnancySupervisor.xlsx b/resources/ResourceFile_PregnancySupervisor.xlsx index b7b42c445d..9dcd06e6f1 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:f03864369f37f0a3d03deffe3b60f1f029172540fd692b1ece19b693979bab95 -size 23759 +oid sha256:cbc67135d0dce5eb308649937ed4395ac435b8da222a1341cc6f3f5cf2a15e99 +size 23856 diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 44712b4ea0..1b458156e4 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1288,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'}) + # # 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) - 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 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 '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 diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 2a59dd0709..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,7 +866,7 @@ 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 @@ -901,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), ) @@ -1641,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']: @@ -1656,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 ------------------------------- @@ -1677,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): @@ -1723,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'): @@ -1736,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) @@ -1764,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) @@ -1790,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): @@ -1808,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) @@ -1846,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': @@ -1856,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 @@ -1866,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) @@ -1910,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 @@ -1920,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) @@ -1944,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, @@ -1976,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 @@ -2000,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): @@ -2031,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 @@ -2075,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(): @@ -2120,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() @@ -2162,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) @@ -2199,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']: @@ -2265,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): @@ -2882,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 @@ -2908,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) @@ -2990,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 ========================================= @@ -3198,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 @@ -3207,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/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index e8f32ed7af..c1b34abae6 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -900,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 @@ -952,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 @@ -959,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): """ diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index dd3dbce209..ef4fab0e79 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -63,7 +63,8 @@ def get_list_of_items(self, item_list): return codes -def check_int_deliverable(self, int_name, hsi_event, q_param, cons, opt_cons=None, equipment=None, dx_test=None): +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 @@ -71,7 +72,6 @@ def check_int_deliverable(self, int_name, hsi_event, q_param, cons, opt_cons=Non 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: add to newborn interventions TODO: what about interventions outside mnh modules : param self: module @@ -127,18 +127,23 @@ def check_int_deliverable(self, int_name, hsi_event, q_param, cons, opt_cons=Non consumables = False test = False - if all([self.rng.random_sample() < value for value in q_param]) or (q_param is None): + 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}) + hsi_event.add_equipment(equipment) - if ((hsi_event.get_consumables(item_codes=cons, optional_item_codes=opt_cons if not None else [])) or - (cons is None)): + 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 (self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run=dx_test, hsi_event=hsi_event) - or (dx_test is None)): + 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: diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py index efb37029bb..9c7e0c172f 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -31,7 +31,7 @@ def test_run_sim_with_mnh_cohort(tmpdir, seed): "*": logging.DEBUG},"directory": tmpdir}) register_modules(sim) - sim.make_initial_population(n=2000) + sim.make_initial_population(n=5000) sim.simulate(end_date=Date(2025, 1, 2)) output= parse_log_file(sim.log_filepath) From 0c3af64a392e4f609b331c67d5cc82121ec06680 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 16 Oct 2024 08:13:20 +0100 Subject: [PATCH 36/38] work on cohort --- .../dummy_cohort_azure_calib.py | 14 ++++++++--- src/tlo/methods/postnatal_supervisor.py | 5 ++-- src/tlo/methods/pregnancy_helper_functions.py | 24 ++++++++++++------ src/tlo/methods/pregnancy_supervisor.py | 25 ++++++++++++------- tests/test_mnh_cohort.py | 2 +- 5 files changed, 47 insertions(+), 23 deletions(-) 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 index 621bb9516f..83ceb3fbf0 100644 --- 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 @@ -6,10 +6,12 @@ outputspath = './outputs/sejjj49@ucl.ac.uk/' scenario_filename = 'cohort_test-2024-10-09T130546Z' +scenario_filename2 = 'cohort_test-2024-10-15T122825Z' -results_folder = get_scenario_outputs(scenario_filename, outputspath)[-1] +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): +def get_data_frames(key, results_folder): def sort_df(_df): _x = _df.drop(columns=['date'], inplace=False) return _x.iloc[0] @@ -24,9 +26,13 @@ def sort_df(_df): return results_df -results = {k:get_data_frames(k) for k in ['mat_comp_incidence', 'nb_comp_incidence', 'deaths_and_stillbirths', - 'service_coverage']} +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 diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 183877f4ed..e0f6708fd5 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -646,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']] @@ -838,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']) diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index ef4fab0e79..d3d36dc070 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -39,13 +39,22 @@ def generate_mnh_outcome_counter(): 'macrosomia', 'small_for_gestational_age', 'early_onset_sepsis', 'late_onset_sepsis', # death outcomes - 'direct_mat_death', 'six_week_survivors', + '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+', - ] + '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} @@ -453,7 +462,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 @@ -481,7 +490,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 @@ -592,7 +602,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 @@ -662,7 +672,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 34ac27a916..c1cf4a67f8 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -1244,6 +1244,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']) @@ -1997,6 +1998,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']) @@ -2278,15 +2280,20 @@ def rate (count, denom, multiplier): 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'] - logger.info(key='deaths_and_stillbirths', - 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)}) + 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+'] diff --git a/tests/test_mnh_cohort.py b/tests/test_mnh_cohort.py index 9c7e0c172f..3c6a46d43d 100644 --- a/tests/test_mnh_cohort.py +++ b/tests/test_mnh_cohort.py @@ -31,7 +31,7 @@ def test_run_sim_with_mnh_cohort(tmpdir, seed): "*": logging.DEBUG},"directory": tmpdir}) register_modules(sim) - sim.make_initial_population(n=5000) + sim.make_initial_population(n=2500) sim.simulate(end_date=Date(2025, 1, 2)) output= parse_log_file(sim.log_filepath) From 53fa8fa1228691d9671e8f6eb765468d2bbdf9f6 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 16 Oct 2024 15:35:06 +0100 Subject: [PATCH 37/38] work on cohort --- .../ResourceFile_PregnancySupervisor.xlsx | 4 ++-- .../cohort_interventions_scenario.py | 18 ++++++++++++++---- .../dummy_cohort_azure_calib.py | 3 ++- src/tlo/methods/pregnancy_helper_functions.py | 11 +++++++---- src/tlo/methods/pregnancy_supervisor.py | 10 ++++++++-- 5 files changed, 33 insertions(+), 13 deletions(-) diff --git a/resources/ResourceFile_PregnancySupervisor.xlsx b/resources/ResourceFile_PregnancySupervisor.xlsx index 9dcd06e6f1..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:cbc67135d0dce5eb308649937ed4395ac435b8da222a1341cc6f3f5cf2a15e99 -size 23856 +oid sha256:d05fc317eafb2761d03ec2107cf5ce5322fac006289ccb1c0366f10e3ab1f685 +size 23984 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 index 5a64abf546..f17877c7a6 100644 --- a/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py +++ b/src/scripts/maternal_perinatal_analyses/cohort_analysis/cohort_interventions_scenario.py @@ -1,3 +1,8 @@ +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 @@ -11,12 +16,12 @@ def __init__(self): self.start_date = Date(2024, 1, 1) self.end_date = Date(2025, 1, 2) self.pop_size = 5000 - self.number_of_draws = 1 + self.number_of_draws = 3 self.runs_per_draw = 10 def log_configuration(self): return { - 'filename': 'cohort_test', 'directory': './outputs', + 'filename': 'block_intervention_test', 'directory': './outputs', "custom_levels": { "*": logging.WARNING, "tlo.methods.demography": logging.INFO, @@ -39,8 +44,13 @@ def modules(self): mnh_cohort_module.MaternalNewbornHealthCohort(resourcefilepath=self.resources)] def draw_parameters(self, draw_number, rng): - return { - } + 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__': 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 index 83ceb3fbf0..711b48c7a0 100644 --- 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 @@ -6,7 +6,8 @@ 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-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] diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index d3d36dc070..d0adf46621 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -95,15 +95,15 @@ def check_int_deliverable(self, int_name, hsi_event, df = self.sim.population.props individual_id = hsi_event.target - int_avail_df = self.sim.modules['PregnancySupervisor'].current_parameters['intervention_availability'] + 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 self.sim.modules['PregnancySupervisor'].current_parameters['ps_analysis_in_progress']: + 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() < int_avail_df.at[int_name, 'availability'] + can_int_run_analysis = self.rng.random_sample() < p_params['intervention_analysis_availability'] # The intervention has no effect if not can_int_run_analysis: @@ -424,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: + 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] diff --git a/src/tlo/methods/pregnancy_supervisor.py b/src/tlo/methods/pregnancy_supervisor.py index c1cf4a67f8..568e39c915 100644 --- a/src/tlo/methods/pregnancy_supervisor.py +++ b/src/tlo/methods/pregnancy_supervisor.py @@ -392,7 +392,12 @@ def __init__(self, name=None, resourcefilepath=None): 'intervention_availability': Parameter( Types.DATA_FRAME, ''), - + 'interventions_analysis': Parameter( + Types.BOOL, ''), + 'interventions_under_analysis': Parameter( + Types.LIST, ''), + 'intervention_analysis_availability': Parameter( + Types.REAL, ''), } PROPERTIES = { @@ -2132,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 From 699b608ae1bfcb51a6951d5caca7daa5572661d9 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 16 Oct 2024 17:05:28 +0100 Subject: [PATCH 38/38] work on cohort --- src/tlo/methods/pregnancy_helper_functions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/pregnancy_helper_functions.py b/src/tlo/methods/pregnancy_helper_functions.py index d0adf46621..91094acc48 100644 --- a/src/tlo/methods/pregnancy_helper_functions.py +++ b/src/tlo/methods/pregnancy_helper_functions.py @@ -424,7 +424,7 @@ def update_current_parameter_dictionary(self, list_position): for key, value in self.parameters.items(): if isinstance(value, list): - if not value: + if not value or (len(value)) == 1: self.current_parameters[key] = self.parameters[key] else: self.current_parameters[key] = self.parameters[key][list_position]