From 93189f333370ca787808d0bf28735fca6eb7c511 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 13:51:39 +0100 Subject: [PATCH 01/28] Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES are declared --- src/tlo/methods/tb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 623ee2e483..36eb662915 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -56,7 +56,7 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): "Epi", } - OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden", "Hiv"} + OPTIONAL_INIT_DEPENDENCIES = {"CardioMetabolicDisorders", "HealthBurden", "Hiv"} METADATA = { Metadata.DISEASE_MODULE, From d98089dbd3f6ec44d8079e13f3285252c2d48cf1 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 13:57:27 +0100 Subject: [PATCH 02/28] Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared --- src/tlo/methods/hiv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index d6455cc861..10b5a637d1 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -81,7 +81,7 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden"} - ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes'} + ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes', 'Schisto', 'CardioMetabolicDisorders'} METADATA = { Metadata.DISEASE_MODULE, From 1030b354617e2cb8e7484070ed7ca72bc1a7fb65 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 14:01:05 +0100 Subject: [PATCH 03/28] Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared --- src/tlo/methods/labour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 35081b7d27..215d946d76 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -78,7 +78,7 @@ def __init__(self, name=None, resourcefilepath=None): ADDITIONAL_DEPENDENCIES = { 'PostnatalSupervisor', 'CareOfWomenDuringPregnancy', 'Lifestyle', 'PregnancySupervisor', 'HealthSystem', 'Contraception', - 'NewbornOutcomes', + 'NewbornOutcomes', 'Depression', 'Hiv' } METADATA = { From fcf1581ebbd4353dd502477053915e90e0fd7c93 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 14:04:50 +0100 Subject: [PATCH 04/28] Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared --- src/tlo/methods/measles.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index 39f9828860..a556007a32 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -27,7 +27,7 @@ class Measles(Module, GenericFirstAppointmentsMixin): INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'SymptomManager'} - OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden'} + OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden', 'Epi'} # declare metadata METADATA = { From 10f438dee348ac47c7ba8e9c29e6d7e4ba66f64c Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 14:08:24 +0100 Subject: [PATCH 05/28] Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared --- src/tlo/methods/newborn_outcomes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 3691bc6003..d6a2455093 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -49,7 +49,8 @@ def __init__(self, name=None, resourcefilepath=None): 'CareOfWomenDuringPregnancy', 'Labour', 'PostnatalSupervisor', - 'PregnancySupervisor' + 'PregnancySupervisor', + 'Hiv' } METADATA = { From 5ab1312c68222daf6d182b09acd99bec32d851f5 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 14:12:04 +0100 Subject: [PATCH 06/28] Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 69ce038299..a822af252f 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -59,7 +59,7 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'PregnancySupervisor'} - ADDITIONAL_DEPENDENCIES = {'Contraception', 'Labour', 'Lifestyle'} + ADDITIONAL_DEPENDENCIES = {'Contraception', 'Labour', 'Lifestyle', 'Hiv', 'Depression', 'Epi', 'Malaria'} METADATA = { Metadata.USES_HEALTHSYSTEM, From a2aef46f3a9cfe00a8892088fccf8bd01351b047 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 14:14:35 +0100 Subject: [PATCH 07/28] Updated modules so that all INIT_DEPENDENCIES and OPTIONAL_INIT_DEPENDENCIES and ADDITIONAL_DEPENDENCIES are declared --- 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 25bce6013f..659f3a4a30 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -44,7 +44,7 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography', 'HealthSystem'} - ADDITIONAL_DEPENDENCIES = {'Labour', 'Lifestyle', 'NewbornOutcomes', 'PregnancySupervisor'} + ADDITIONAL_DEPENDENCIES = {'Labour', 'Lifestyle', 'NewbornOutcomes', 'PregnancySupervisor', 'Hiv', 'CareOfWomenDuringPregnancy'} METADATA = {Metadata.DISEASE_MODULE, Metadata.USES_HEALTHSYSTEM, From d58811716633b81a10a200eaf0da498fd99f0066 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 14:54:34 +0100 Subject: [PATCH 08/28] Moved all new dependencies to OPTIONAL_INIT_DEPENDENCIES --- src/tlo/methods/care_of_women_during_pregnancy.py | 4 ++-- src/tlo/methods/hiv.py | 4 ++-- src/tlo/methods/labour.py | 5 ++--- src/tlo/methods/measles.py | 4 ++-- src/tlo/methods/newborn_outcomes.py | 4 +--- src/tlo/methods/postnatal_supervisor.py | 4 +++- 6 files changed, 12 insertions(+), 13 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index a822af252f..7bfb3bc1d2 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -57,9 +57,9 @@ def __init__(self, name=None, resourcefilepath=None): # Finally set up a counter for ANC visits. self.anc_counter = dict() - INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'PregnancySupervisor'} + INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'PregnancySupervisor', 'Hiv', 'Depression', 'Epi', 'Malaria'} - ADDITIONAL_DEPENDENCIES = {'Contraception', 'Labour', 'Lifestyle', 'Hiv', 'Depression', 'Epi', 'Malaria'} + ADDITIONAL_DEPENDENCIES = {'Contraception', 'Labour', 'Lifestyle'} METADATA = { Metadata.USES_HEALTHSYSTEM, diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index 10b5a637d1..235a714245 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -79,9 +79,9 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): INIT_DEPENDENCIES = {"Demography", "HealthSystem", "Lifestyle", "SymptomManager"} - OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden"} + OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden", 'Schisto', 'CardioMetabolicDisorders'} - ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes', 'Schisto', 'CardioMetabolicDisorders'} + ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes'} METADATA = { Metadata.DISEASE_MODULE, diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 215d946d76..9f9f7cfed7 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -73,12 +73,11 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography'} - OPTIONAL_INIT_DEPENDENCIES = {'Stunting'} + OPTIONAL_INIT_DEPENDENCIES = {'Stunting', 'NewbornOutcomes', 'Depression', 'Hiv'} ADDITIONAL_DEPENDENCIES = { 'PostnatalSupervisor', 'CareOfWomenDuringPregnancy', 'Lifestyle', 'PregnancySupervisor', - 'HealthSystem', 'Contraception', - 'NewbornOutcomes', 'Depression', 'Hiv' + 'HealthSystem', 'Contraception' } METADATA = { diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index a556007a32..d3375bab2d 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -25,9 +25,9 @@ class Measles(Module, GenericFirstAppointmentsMixin): """This module represents measles infections and disease.""" - INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'SymptomManager'} + INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'SymptomManager', 'Epi'} - OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden', 'Epi'} + OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden'} # declare metadata METADATA = { diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index d6a2455093..01c1487316 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -43,14 +43,12 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography', 'HealthSystem'} - OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden'} + OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden', 'PregnancySupervisor', 'Hiv'} ADDITIONAL_DEPENDENCIES = { 'CareOfWomenDuringPregnancy', 'Labour', 'PostnatalSupervisor', - 'PregnancySupervisor', - 'Hiv' } METADATA = { diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 659f3a4a30..01c1e30106 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -44,7 +44,9 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography', 'HealthSystem'} - ADDITIONAL_DEPENDENCIES = {'Labour', 'Lifestyle', 'NewbornOutcomes', 'PregnancySupervisor', 'Hiv', 'CareOfWomenDuringPregnancy'} + ADDITIONAL_DEPENDENCIES = {'Labour', 'Lifestyle', 'NewbornOutcomes', 'PregnancySupervisor'} + + OPTIONAL_INIT_DEPENDENCIES = {'Hiv', 'CareOfWomenDuringPregnancy'} METADATA = {Metadata.DISEASE_MODULE, Metadata.USES_HEALTHSYSTEM, From 0fc8682190fabc770f8ed9f451431125ed57aaf2 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 14:57:07 +0100 Subject: [PATCH 09/28] Type --- .../methods/care_of_women_during_pregnancy.py | 60 ++++++++----------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 7bfb3bc1d2..7635f35589 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -57,7 +57,9 @@ def __init__(self, name=None, resourcefilepath=None): # Finally set up a counter for ANC visits. self.anc_counter = dict() - INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'PregnancySupervisor', 'Hiv', 'Depression', 'Epi', 'Malaria'} + INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'PregnancySupervisor'} + + OPTIONAL_INIT_DEPENDENCIES = {'Hiv', 'Depression', 'Epi', 'Malaria'} ADDITIONAL_DEPENDENCIES = {'Contraception', 'Labour', 'Lifestyle'} @@ -307,7 +309,7 @@ def get_and_store_pregnancy_item_codes(self): self.item_codes_preg_consumables['syphilis_test'] = {ic('Test, Rapid plasma reagin (RPR)'): 1} # ------------------------------------------- SYPHILIS TREATMENT ---------------------------------------------- - self.item_codes_preg_consumables['syphilis_treatment'] =\ + self.item_codes_preg_consumables['syphilis_treatment'] = \ {ic('Benzathine benzylpenicillin, powder for injection, 2.4 million IU'): 1} # ----------------------------------------------- GDM TEST ---------------------------------------------------- @@ -395,7 +397,6 @@ def initialise_simulation(self, sim): sensitivity=params['sensitivity_urine_protein_1_plus'], specificity=params['specificity_urine_protein_1_plus']), - # This test represents point of care haemoglobin testing used in ANC to detect anaemia (all-severity) point_of_care_hb_test=DxTest( property='ps_anaemia_in_pregnancy', @@ -611,7 +612,7 @@ def calculate_visit_date_and_schedule_visit(visit): # We subtract this woman's current gestational age from the recommended gestational age for the next # contact weeks_due_next_visit = int(recommended_gestation_next_anc - df.at[individual_id, - 'ps_gestational_age_in_weeks']) + 'ps_gestational_age_in_weeks']) # And use this value as the number of weeks until she is required to return for her next ANC visit_date = self.sim.date + DateOffset(weeks=weeks_due_next_visit) @@ -739,8 +740,7 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # 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): - + 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'}) @@ -754,10 +754,9 @@ def screening_interventions_delivered_at_every_contact(self, 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\ + 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) @@ -767,10 +766,9 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # 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: - if (df.at[person_id, 'ps_htn_disorders'] == 'severe_pre_eclamp') or\ + if (df.at[person_id, 'ps_htn_disorders'] == 'severe_pre_eclamp') or \ (df.at[person_id, 'ps_htn_disorders'] == 'eclampsia') or \ - not df.at[person_id, 'ac_gest_htn_on_treatment']: - + not df.at[person_id, 'ac_gest_htn_on_treatment']: df.at[person_id, 'ac_to_be_admitted'] = True # Here we conduct screening and initiate treatment for depression as needed @@ -793,7 +791,7 @@ def iron_and_folic_acid_supplementation(self, hsi_event): # check consumable availability - dose is total days of pregnancy x 2 tablets 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()} + 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) @@ -809,7 +807,6 @@ def iron_and_folic_acid_supplementation(self, hsi_event): # Women started on IFA at this stage may already be anaemic, we here apply a probability that # starting on a course of IFA will correct anaemia prior to follow up if self.rng.random_sample() < params['effect_of_ifa_for_resolving_anaemia']: - # Store date of resolution for daly calculations pregnancy_helper_functions.store_dalys_in_mni( person_id, mni, f'{df.at[person_id, "ps_anaemia_in_pregnancy"]}_anaemia_resolution', @@ -829,7 +826,7 @@ def balance_energy_and_protein_supplementation(self, hsi_event): # If the consumables are available... days = self.get_approx_days_of_pregnancy(person_id) - updated_cons = {k: v*days for (k, v) in + 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( @@ -874,7 +871,6 @@ def tetanus_vaccination(self, hsi_event): person_id = hsi_event.target if 'Epi' in self.sim.modules: - # Define the HSI in which the vaccine is delivered vaccine_hsi = HSI_TdVaccine(self.sim.modules['Epi'], person_id=person_id, suppress_footprint=True) @@ -1003,7 +999,7 @@ def syphilis_screening_and_treatment(self, hsi_event): 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) + 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: @@ -1070,7 +1066,7 @@ def gdm_screening(self, hsi_event): # We check if this woman has any of the key risk factors, if so they are sent for additional blood tests 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']: + 'ps_prev_stillbirth']: # If they are available, the test is conducted if self.rng.random_sample() < params['prob_intervention_delivered_gdm_test']: @@ -1095,7 +1091,6 @@ def gdm_screening(self, hsi_event): # 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) @@ -1140,13 +1135,12 @@ def check_anc1_can_run(self, individual_id, gest_age_next_contact): (date_difference > pd.to_timedelta(7, unit='D')) or (df.at[individual_id, 'ac_total_anc_visits_current_pregnancy'] > 0) or (df.at[individual_id, 'ps_gestational_age_in_weeks'] < 7) - ): + ): return False # If the woman is an inpatient when ANC1 is scheduled, she will try and return at the next appropriate # gestational age if df.at[individual_id, 'hs_is_inpatient']: - # We assume that she will return for her first appointment at the next gestation in the schedule logger.debug(key='message', data=f'mother {individual_id} is scheduled to attend ANC today but is ' f'currently an inpatient- she will be scheduled to arrive at her next ' @@ -1221,7 +1215,7 @@ def full_blood_count_testing(self, hsi_event): 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) + dx_tests_to_run='full_blood_count_hb', hsi_event=hsi_event) if test_result and (df.at[person_id, 'ps_anaemia_in_pregnancy'] == 'none'): return 'non_severe' @@ -1322,7 +1316,7 @@ def initiate_treatment_for_severe_hypertension(self, individual_id, hsi_event): # disease (as the disease is multi-system and hypertension is only one contributing factor to mortality) but # instead use this property to reduce risk of acute death from this episode of disease if (df.at[individual_id, 'ps_htn_disorders'] == 'severe_pre_eclamp') or (df.at[individual_id, - 'ps_htn_disorders'] == + 'ps_htn_disorders'] == 'eclampsia'): df.at[individual_id, 'ac_iv_anti_htn_treatment'] = True @@ -1416,7 +1410,7 @@ def calculate_beddays(self, individual_id): # they have reached that gestation elif ((mother.ps_placenta_praevia and (mother.ps_antepartum_haemorrhage == 'mild_moderate')) or (mother.ps_premature_rupture_of_membranes and not mother.ps_chorioamnionitis)) and \ - (mother.ps_gestational_age_in_weeks < 37): + (mother.ps_gestational_age_in_weeks < 37): beddays = int((37 * 7) - (mother.ps_gestational_age_in_weeks * 7)) else: @@ -1857,7 +1851,7 @@ def apply(self, person_id, squeeze_factor): # =================================== INTERVENTIONS ==================================================== self.add_equipment({'Weighing scale', 'Measuring tapes', - 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 'Stethoscope, foetal, monaural, Pinard, plastic'}) self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) @@ -2002,6 +1996,7 @@ class HSI_CareOfWomenDuringPregnancy_FocusedANCVisit(HSI_Event, IndividualScopeE within some analyses as the scheduled of interventions per visit is different from the ANC8 structure. This event represents all four ANC visits. """ + def __init__(self, module, person_id, visit_number): super().__init__(module, person_id=person_id) assert isinstance(module, CareOfWomenDuringPregnancy) @@ -2097,7 +2092,6 @@ def apply(self, person_id, squeeze_factor): # Following this the woman's next visit is scheduled (if she hasn't already attended 4 visits) if self.visit_number < 4: - # update the visit number for the event scheduling self.visit_number = self.visit_number + 1 @@ -2131,7 +2125,7 @@ def apply(self, person_id, squeeze_factor): # If the woman is no longer alive, pregnant is in labour or is an inpatient already then the event doesnt run if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'is_pregnant'] or \ - df.at[person_id, 'la_currently_in_labour'] or df.at[person_id, 'hs_is_inpatient']: + df.at[person_id, 'la_currently_in_labour'] or df.at[person_id, 'hs_is_inpatient']: return # We set this admission property to show shes being admitted for induction of labour and hand her over to the @@ -2219,7 +2213,6 @@ def apply(self, person_id, squeeze_factor): follow_up_date = self.sim.date + DateOffset(days=28) if pd.isnull(mother.ac_date_next_contact) or ((mother.ac_date_next_contact - self.sim.date) > pd.to_timedelta(28, unit='D')): - outpatient_checkup = HSI_CareOfWomenDuringPregnancy_AntenatalOutpatientManagementOfAnaemia( self.sim.modules['CareOfWomenDuringPregnancy'], person_id=person_id) @@ -2290,7 +2283,7 @@ def apply(self, person_id, squeeze_factor): # to determine mode of delivery here if mother.ps_htn_disorders == 'eclampsia': df.at[person_id, 'ac_admitted_for_immediate_delivery'] = self.module.rng.choice( - delivery_mode, p=params['prob_delivery_modes_ec']) + delivery_mode, p=params['prob_delivery_modes_ec']) elif mother.ps_htn_disorders == 'severe_pre_eclamp': df.at[person_id, 'ac_admitted_for_immediate_delivery'] = self.module.rng.choice( @@ -2324,7 +2317,7 @@ def apply(self, person_id, squeeze_factor): # bleed and her current gestation at the time of bleeding if ((mother.ps_antepartum_haemorrhage == 'severe') and mother.ps_gestational_age_in_weeks >= 28) or \ - (mother.ps_gestational_age_in_weeks >= 37): + (mother.ps_gestational_age_in_weeks >= 37): # Women experiencing severe bleeding are admitted immediately for caesarean section df.at[person_id, 'ac_admitted_for_immediate_delivery'] = 'caesarean_now' mni[person_id]['cs_indication'] = 'an_aph_pp' @@ -2443,7 +2436,6 @@ def apply(self, person_id, squeeze_factor): # If she is determined to still be anaemic she is admitted for additional treatment via the inpatient event if fbc_result in ('mild', 'moderate', 'severe'): - admission = HSI_CareOfWomenDuringPregnancy_AntenatalWardInpatientCare( self.sim.modules['CareOfWomenDuringPregnancy'], person_id=person_id) @@ -2468,6 +2460,7 @@ class HSI_CareOfWomenDuringPregnancy_AntenatalOutpatientManagementOfGestationalD pregnancy. This event manages repeat blood testing for women who were found to have GDM and treated. If the woman remains hyperglycaemic she is moved to the next line treatment and scheduled to return for follow up. """ + def __init__(self, module, person_id): super().__init__(module, person_id=person_id) assert isinstance(module, CareOfWomenDuringPregnancy) @@ -2487,7 +2480,7 @@ def apply(self, person_id, squeeze_factor): return if not mother.la_currently_in_labour and not mother.hs_is_inpatient and mother.ps_gest_diab != 'none' \ - and (mother.ac_gest_diab_on_treatment != 'none') and (mother.ps_gestational_age_in_weeks > 21): + and (mother.ac_gest_diab_on_treatment != 'none') and (mother.ps_gestational_age_in_weeks > 21): est_length_preg = self.module.get_approx_days_of_pregnancy(person_id) @@ -2520,7 +2513,7 @@ def schedule_gdm_event_and_checkup(): 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) + self.module, self, cons=updated_cons, opt_cons=None) # If the meds are available women are started on that treatment if avail: @@ -2538,7 +2531,7 @@ def schedule_gdm_event_and_checkup(): # Dose is (avg.) 0.8 units per KG per day. Average weight is an appoximation required_units_per_preg = 65 * (0.8 * est_length_preg) - required_vials = np.ceil(required_units_per_preg/1000) + required_vials = np.ceil(required_units_per_preg / 1000) updated_cons = {k: v * required_vials for (k, v) in self.module.item_codes_preg_consumables['insulin_treatment'].items()} @@ -2719,7 +2712,6 @@ def __init__(self, module): super().__init__(module, frequency=DateOffset(months=self.repeat)) def apply(self, population): - yearly_counts = self.module.anc_counter logger.info(key='anc_visits_which_ran', data=yearly_counts) self.module.anc_counter = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0} From 3938a9f022e373d11ae49763dc204a3e4d000b1d Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 15:20:09 +0100 Subject: [PATCH 10/28] Typo --- src/tlo/methods/newborn_outcomes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 01c1487316..fca30f4059 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -43,12 +43,13 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography', 'HealthSystem'} - OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden', 'PregnancySupervisor', 'Hiv'} + OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden', 'Hiv'} ADDITIONAL_DEPENDENCIES = { 'CareOfWomenDuringPregnancy', 'Labour', 'PostnatalSupervisor', + 'PregnancySupervisor' } METADATA = { From 1e0b36fb475faf9f5e2ca0e2e8c044fcf1e397aa Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 15:30:12 +0100 Subject: [PATCH 11/28] Updated circular dependencies --- src/tlo/methods/hiv.py | 4 ++-- src/tlo/methods/tb.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index 235a714245..47ccd92cff 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -79,9 +79,9 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): INIT_DEPENDENCIES = {"Demography", "HealthSystem", "Lifestyle", "SymptomManager"} - OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden", 'Schisto', 'CardioMetabolicDisorders'} + OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden", 'Schisto'} - ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes'} + ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes', 'CardioMetabolicDisorders'} METADATA = { Metadata.DISEASE_MODULE, diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 36eb662915..b5c49a22a3 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -56,7 +56,9 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): "Epi", } - OPTIONAL_INIT_DEPENDENCIES = {"CardioMetabolicDisorders", "HealthBurden", "Hiv"} + OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden", "Hiv"} + + ADDITIONAL_DEPENDENCIES = {"CardioMetabolicDisorders", } METADATA = { Metadata.DISEASE_MODULE, From 82b63c081e8d0038cf69148b500c780a2975dcbb Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 15:34:22 +0100 Subject: [PATCH 12/28] Updated circular dependencies --- src/tlo/methods/hiv.py | 4 ++-- src/tlo/methods/labour.py | 4 ++-- src/tlo/methods/measles.py | 4 +++- src/tlo/methods/newborn_outcomes.py | 5 +++-- src/tlo/methods/postnatal_supervisor.py | 5 ++--- src/tlo/methods/tb.py | 2 +- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index 47ccd92cff..07b5c6775b 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -79,9 +79,9 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): INIT_DEPENDENCIES = {"Demography", "HealthSystem", "Lifestyle", "SymptomManager"} - OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden", 'Schisto'} + OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden",} - ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes', 'CardioMetabolicDisorders'} + ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes', 'Schisto', 'CardioMetabolicDisorders'} METADATA = { Metadata.DISEASE_MODULE, diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 9f9f7cfed7..48cf1c3eb3 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -73,11 +73,11 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography'} - OPTIONAL_INIT_DEPENDENCIES = {'Stunting', 'NewbornOutcomes', 'Depression', 'Hiv'} + OPTIONAL_INIT_DEPENDENCIES = {'Stunting'} ADDITIONAL_DEPENDENCIES = { 'PostnatalSupervisor', 'CareOfWomenDuringPregnancy', 'Lifestyle', 'PregnancySupervisor', - 'HealthSystem', 'Contraception' + 'HealthSystem', 'Contraception', 'NewbornOutcomes', 'Depression', 'Hiv' } METADATA = { diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index d3375bab2d..020d7d80fc 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -25,10 +25,12 @@ class Measles(Module, GenericFirstAppointmentsMixin): """This module represents measles infections and disease.""" - INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'SymptomManager', 'Epi'} + INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'SymptomManager'} OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden'} + ADDITIONAL_DEPENDENCIES = {'Epi'} + # declare metadata METADATA = { Metadata.DISEASE_MODULE, diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index fca30f4059..d6a2455093 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -43,13 +43,14 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography', 'HealthSystem'} - OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden', 'Hiv'} + OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden'} ADDITIONAL_DEPENDENCIES = { 'CareOfWomenDuringPregnancy', 'Labour', 'PostnatalSupervisor', - 'PregnancySupervisor' + 'PregnancySupervisor', + 'Hiv' } METADATA = { diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 01c1e30106..dc1d3f2d2e 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -44,9 +44,8 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography', 'HealthSystem'} - ADDITIONAL_DEPENDENCIES = {'Labour', 'Lifestyle', 'NewbornOutcomes', 'PregnancySupervisor'} - - OPTIONAL_INIT_DEPENDENCIES = {'Hiv', 'CareOfWomenDuringPregnancy'} + ADDITIONAL_DEPENDENCIES = {'Labour', 'Lifestyle', 'NewbornOutcomes', 'PregnancySupervisor', + 'Hiv', 'CareOfWomenDuringPregnancy'} METADATA = {Metadata.DISEASE_MODULE, Metadata.USES_HEALTHSYSTEM, diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index b5c49a22a3..76e5b74d38 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -58,7 +58,7 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden", "Hiv"} - ADDITIONAL_DEPENDENCIES = {"CardioMetabolicDisorders", } + ADDITIONAL_DEPENDENCIES = {"CardioMetabolicDisorders"} METADATA = { Metadata.DISEASE_MODULE, From 22cfd7d80108b0fe069dda6c583ccb7f03a3bbe9 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Fri, 4 Oct 2024 16:21:23 +0100 Subject: [PATCH 13/28] Changed to additional dependencies --- src/tlo/methods/care_of_women_during_pregnancy.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 7635f35589..27b2b7bf3d 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -59,9 +59,7 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'PregnancySupervisor'} - OPTIONAL_INIT_DEPENDENCIES = {'Hiv', 'Depression', 'Epi', 'Malaria'} - - ADDITIONAL_DEPENDENCIES = {'Contraception', 'Labour', 'Lifestyle'} + ADDITIONAL_DEPENDENCIES = {'Contraception', 'Labour', 'Lifestyle', 'Hiv', 'Epi', 'Depression', 'Malaria'} METADATA = { Metadata.USES_HEALTHSYSTEM, From 6f867411f4f24c3da10363b5d2d36830e08249f4 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Tue, 8 Oct 2024 14:18:27 +0100 Subject: [PATCH 14/28] Added subgraphs --- .../get_properties/properties_graph.py | 123 ++++++++++++++++++ .../property_dependency_graph.py | 114 ++++++++++++++++ src/tlo/analysis/module_dependencies_graph.py | 111 ++++++++++++++++ 3 files changed, 348 insertions(+) create mode 100644 src/scripts/get_properties/properties_graph.py create mode 100644 src/scripts/longterm_projections/property_dependency_graph.py create mode 100644 src/tlo/analysis/module_dependencies_graph.py diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py new file mode 100644 index 0000000000..125ad5248c --- /dev/null +++ b/src/scripts/get_properties/properties_graph.py @@ -0,0 +1,123 @@ +"""Functions for getting, checking, and sorting properties of ``Module`` subclasses.""" + +from typing import Any, Dict, Mapping, Set, Type, Union +import inspect +import importlib +import os +import pkgutil + +import tlo.methods +from tlo import Module +from tlo.methods.hiv import Hiv +from tlo.methods.tb import Tb + + +def get_properties( + module: Union[Module, Type[Module]], +) -> Set[str]: + """Get the properties for a ``Module`` subclass. + + :param module: ``Module`` subclass to get properties for. + :return: Set of ``Module`` subclass names corresponding to properties of ``module``. + """ + return module.PROPERTIES + + +def check_properties_in_module(module: Any, properties: Set[str]) -> Set[str]: + """Check if any of the properties are used in the given module's script.""" + used_properties = set() + + # Get the source code of the module + source_code = inspect.getsource(module) + + # Check each property for usage in the source code + for prop in properties: + if prop in source_code: + used_properties.add(prop) + + return used_properties + + +def is_valid_tlo_module_subclass(obj: Any, excluded_modules: Set[str]) -> bool: + """Check if the object is a valid TLO Module subclass.""" + return isinstance(obj, type) and issubclass(obj, Module) and obj.__name__ not in excluded_modules + + +def get_module_property_map(excluded_modules: Set[str]) -> Mapping[str, Set[Type[Module]]]: + """Constructs a map from property names to sets of Module subclass objects. + + :param excluded_modules: Set of Module subclass names to exclude from the map. + + :return: A mapping from property names to sets of corresponding Module subclass objects. + This adds an implicit requirement that the names of all the Module subclasses are unique. + + :raises RuntimeError: Raised if multiple Module subclasses with the same name are defined + (and not included in the excluded_modules set). + """ + methods_package_path = os.path.dirname(inspect.getfile(tlo.methods)) + module_property_map: Dict[str, Set[Type[Module]]] = {} + + for _, methods_module_name, _ in pkgutil.iter_modules([methods_package_path]): + methods_module = importlib.import_module(f'tlo.methods.{methods_module_name}') + for _, obj in inspect.getmembers(methods_module): + if is_valid_tlo_module_subclass(obj, excluded_modules): + properties = get_properties(obj) + + for prop in properties: + if prop not in module_property_map: + module_property_map[prop] = set() + module_property_map[prop].add(obj) + + return module_property_map + + +# Get properties from your target Module subclass +module_properties = get_properties(Hiv) + +# Use the function to find used properties +used_properties = check_properties_in_module(Tb, module_properties) + +# Print the results +if used_properties: + print("The following properties are used in the other module's script:") + for prop in used_properties: + print(f"- {prop}") +else: + print("No properties are used in the other module's script.") + +# Example usage of get_module_property_map +excluded = {'SomeExcludedModule'} # Specify any excluded modules +property_map = get_module_property_map(excluded) + + +def get_module_property_map(excluded_modules: Set[str]) -> Mapping[str, Set[Type[Module]]]: + """Constructs a map from property names to sets of Module subclass objects. + + :param excluded_modules: Set of Module subclass names to exclude from the map. + + :return: A mapping from property names to sets of corresponding Module subclass objects. + This adds an implicit requirement that the names of all the Module subclasses are unique. + + :raises RuntimeError: Raised if multiple Module subclasses with the same name are defined + (and not included in the excluded_modules set). + """ + methods_package_path = os.path.dirname(inspect.getfile(tlo.methods)) + module_property_map: Dict[str, Set[Type[Module]]] = {} + + for _, methods_module_name, _ in pkgutil.iter_modules([methods_package_path]): + methods_module = importlib.import_module(f'tlo.methods.{methods_module_name}') + for _, obj in inspect.getmembers(methods_module): + if is_valid_tlo_module_subclass(obj, excluded_modules): + properties = get_properties(obj) + + for prop in properties: + if prop not in module_property_map: + module_property_map[prop] = set() + module_property_map[prop].add(obj) + + return module_property_map + + +# Print the property map for verification +for prop, modules in property_map.items(): + print(f"Property '{prop}' is provided by the following modules: {', '.join(module.__name__ for module in modules)}") diff --git a/src/scripts/longterm_projections/property_dependency_graph.py b/src/scripts/longterm_projections/property_dependency_graph.py new file mode 100644 index 0000000000..8755c45e8d --- /dev/null +++ b/src/scripts/longterm_projections/property_dependency_graph.py @@ -0,0 +1,114 @@ +"""Construct a graph showing dependencies between modules.""" + +import argparse +from pathlib import Path +from types import MappingProxyType +from typing import Any, Dict, Mapping, Set, Type, Union +import numpy as np +import importlib +import inspect +import os +import pkgutil +import pydot + +import tlo.methods +from tlo import Module +from tlo.methods.hiv import Hiv +from tlo.methods.tb import Tb +from tlo.dependencies import DependencyGetter, get_all_dependencies, is_valid_tlo_module_subclass +from tlo.methods import Metadata + +SHORT_TREATMENT_ID_TO_COLOR_MAP = MappingProxyType({ + # Define your color mappings here + '*': 'black', + 'FirstAttendance*': 'darkgrey', + # ... (other mappings) +}) + + +def _standardize_short_treatment_id(short_treatment_id: str) -> str: + return short_treatment_id.replace('_*', '*').rstrip('*') + '*' + + +def get_color_short_treatment_id(short_treatment_id: str) -> str: + """Return the colour assigned to this shorted TREATMENT_ID.""" + return SHORT_TREATMENT_ID_TO_COLOR_MAP.get( + _standardize_short_treatment_id(short_treatment_id), np.nan + ) + + +def get_properties(module: Union[Module, Type[Module]]) -> Set[str]: + """Get the properties for a ``Module`` subclass.""" + return module.PROPERTIES + + +def check_properties_in_module(module: Any, properties: Set[str]) -> Set[str]: + """Check if any of the properties are used in the given module's script.""" + used_properties = set() + source_code = inspect.getsource(module) + + for prop in properties: + if prop in source_code: + used_properties.add(prop) + + return used_properties + + +def get_module_property_map(excluded_modules: Set[str]) -> Mapping[str, Set[Type[Module]]]: + """Constructs a map from property names to sets of Module subclass objects.""" + methods_package_path = os.path.dirname(inspect.getfile(tlo.methods)) + module_property_map: Dict[str, Set[Type[Module]]] = {} + + for _, methods_module_name, _ in pkgutil.iter_modules([methods_package_path]): + methods_module = importlib.import_module(f'tlo.methods.{methods_module_name}') + for _, obj in inspect.getmembers(methods_module): + if is_valid_tlo_module_subclass(obj, excluded_modules): + properties = get_properties(obj) + for prop in properties: + if prop not in module_property_map: + module_property_map[prop] = set() + module_property_map[prop].add(obj) + + return module_property_map + + +def construct_module_dependency_graph( + excluded_modules: Set[str], + get_dependencies: DependencyGetter = get_all_dependencies, +): + """Construct a pydot object representing the module dependency graph.""" + if pydot is None: + raise RuntimeError("pydot package must be installed") + + module_class_map = get_module_property_map(excluded_modules) + module_graph = pydot.Dot("modules", graph_type="digraph", rankdir='LR') + + for key, module in module_class_map.items(): + for dependency in get_properties(module): + if dependency not in excluded_modules: + module_graph.add_edge(pydot.Edge(dependency, key)) + + return module_graph + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "output_file", type=Path, help=( + "Path to output graph to. File extension will determine output format - for example: dot, dia, png, svg" + ) + ) + args = parser.parse_args() + + excluded_modules = { + "Mockitis", + "ChronicSyndrome", + "Skeleton", + } + + module_graph = construct_module_dependency_graph(excluded_modules) + + format = ( + args.output_file.suffix[1:] if args.output_file.suffix else "raw" + ) + module_graph.write(args.output_file, format=format) diff --git a/src/tlo/analysis/module_dependencies_graph.py b/src/tlo/analysis/module_dependencies_graph.py new file mode 100644 index 0000000000..53812ec268 --- /dev/null +++ b/src/tlo/analysis/module_dependencies_graph.py @@ -0,0 +1,111 @@ +"""Construct a graph showing dependencies between modules.""" + +import argparse +from pathlib import Path +from typing import Dict, Set + +from tlo.dependencies import DependencyGetter, get_all_dependencies, get_module_class_map +from tlo.methods import Metadata + +try: + import pydot +except ImportError: + pydot = None + + +def construct_module_dependency_graph( + excluded_modules: Set[str], + disease_module_node_defaults: Dict, + other_module_node_defaults: Dict, + get_dependencies: DependencyGetter = get_all_dependencies, +): + """Construct a pydot object representing module dependency graph. + :param excluded_modules: Set of ``Module`` subclass names to not included in graph. + :param disease_module_node_defaults: Any dot node attributes to apply to by default + to disease module nodes. + :param other_module_node_defaults: Any dot node attributes to apply to by default + to non-disease module nodes. + :param get_dependencies: Function which given a module gets the set of module + dependencies. Defaults to extracting all dependencies. + :return: Pydot directed graph representing module dependencies. + """ + if pydot is None: + raise RuntimeError("pydot package must be installed") + + module_class_map = get_module_class_map(excluded_modules) + module_graph = pydot.Dot("modules", graph_type="digraph") + disease_module_subgraph = pydot.Subgraph("disease_modules") + module_graph.add_subgraph(disease_module_subgraph) + other_module_subgraph = pydot.Subgraph("other_modules") + module_graph.add_subgraph(other_module_subgraph) + + # Set default styles for nodes + disease_module_node_defaults["style"] = "filled" + other_module_node_defaults["style"] = "filled" + + for name, module_class in module_class_map.items(): + # Determine attributes based on module type + node_attributes = {} + + if Metadata.DISEASE_MODULE in module_class.METADATA and name.endswith("Cancer"): + node_attributes.update(disease_module_node_defaults) + node_attributes["color"] = "lightblue" # Color for disease modules and Cancer + else: + node_attributes.update(other_module_node_defaults) + node_attributes["color"] = "lightgreen" # Default color for other modules + + # Create the node with determined attributes + node = pydot.Node(name, **node_attributes) + + # Add the node to the appropriate subgraph + if Metadata.DISEASE_MODULE in module_class.METADATA or name.endswith("Cancer"): + disease_module_subgraph.add_node(node) + else: + other_module_subgraph.add_node(node) + + for key, module in module_class_map.items(): + for dependency in get_dependencies(module, module_class_map.keys()): + if dependency not in excluded_modules: + module_graph.add_edge(pydot.Edge(key, dependency)) + + return module_graph + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "output_file", type=Path, help=( + "Path to output graph to. File extension will determine output format - for example: dot, dia, png, svg" + ) + ) + args = parser.parse_args() + + excluded_modules = { + "Mockitis", + "ChronicSyndrome", + "Skeleton", + "AlriPropertiesOfOtherModules", + "DiarrhoeaPropertiesOfOtherModules", + "DummyHivModule", + "SimplifiedBirths", + "Demography", + "HealthBurden", + "SymptomManager", + "DummyTbModule", + "ImprovedHealthSystemAndCareSeekingScenarioSwitcher", + "HealthSeekingBehaviour", + "HealthSystem", + "Deviance", + "SimplifiedPregnancyAndLabour" + } + + module_graph = construct_module_dependency_graph( + excluded_modules, + disease_module_node_defaults={"fontname": "Arial", "shape": "box"}, + other_module_node_defaults={"fontname": "Arial", "shape": "ellipse"}, + ) + + format = ( + args.output_file.suffix[1:] if args.output_file.suffix else "raw" + ) + module_graph.write(args.output_file, format=format) From 9adb1a42f310db215bc7a348b86f2fd052566987 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Tue, 8 Oct 2024 14:22:42 +0100 Subject: [PATCH 15/28] Graph of properties from disease modules that contribute to other disease modules. Process: collects properties from each disease (+lifestyle) module, and compares them to the scripts of the remaining disease (+lifestyle) module. --- .../get_properties/properties_graph.py | 301 ++++++++++++++---- 1 file changed, 233 insertions(+), 68 deletions(-) diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py index 125ad5248c..162627bb99 100644 --- a/src/scripts/get_properties/properties_graph.py +++ b/src/scripts/get_properties/properties_graph.py @@ -1,16 +1,78 @@ -"""Functions for getting, checking, and sorting properties of ``Module`` subclasses.""" +"""Construct a graph showing dependencies between modules.""" -from typing import Any, Dict, Mapping, Set, Type, Union -import inspect +import argparse import importlib +import inspect import os +from pathlib import Path +from types import MappingProxyType +from typing import Any, Callable, Generator, Iterable, Mapping, Optional, Set, Type, Union + +import numpy as np import pkgutil +import pydot import tlo.methods from tlo import Module +from tlo.methods import Metadata +from tlo.dependencies import DependencyGetter, get_all_dependencies, is_valid_tlo_module_subclass from tlo.methods.hiv import Hiv from tlo.methods.tb import Tb +try: + import pydot +except ImportError: + pydot = None +SHORT_TREATMENT_ID_TO_COLOR_MAP = MappingProxyType({ + '*': 'black', + 'FirstAttendance*': 'darkgrey', + 'Inpatient*': 'silver', + 'Contraception*': 'darkseagreen', + 'AntenatalCare*': 'green', + 'DeliveryCare*': 'limegreen', + 'PostnatalCare*': 'springgreen', + 'CareOfWomenDuringPregnancy*': '#4D804D', + 'Labour*': '#19A719', + 'NewbornOutcomes*': '#19E659', + 'PostnatalSupervisor*': '#5D8C5D', + 'PregnancySupervisor*': '#27C066', + 'Alri*': 'darkorange', + 'Diarrhoea*': 'tan', + 'Undernutrition*': 'gold', + 'Epi*': 'darkgoldenrod', + 'Stunting*': '#D58936', + 'StuntingPropertiesOfOtherModules*': "#EAC143", + 'Wasting*': '#DE9F0E', + 'Hiv*': 'deepskyblue', + 'Malaria*': 'lightsteelblue', + 'Measles*': 'cornflowerblue', + 'Tb*': 'mediumslateblue', + 'Schisto*': 'skyblue', + 'CardioMetabolicDisorders*': 'brown', + 'BladderCancer*': 'orchid', + 'BreastCancer*': 'mediumvioletred', + 'OesophagealCancer*': 'deeppink', + 'ProstateCancer*': 'hotpink', + 'OtherAdultCancer*': 'palevioletred', + 'Depression*': 'indianred', + 'Epilepsy*': 'red', + 'Copd*': 'lightcoral', + 'RTI*': 'lightsalmon', + 'Lifestyle*': 'silver', +}) + +def _standardize_short_treatment_id(short_treatment_id): + return short_treatment_id.replace('_*', '*').rstrip('*') + '*' + + +def get_color_short_treatment_id(short_treatment_id: str) -> str: + """Return the colour assigned to this shorted TREATMENT_ID. + + Returns `np.nan` if treatment_id is not recognised. + """ + return SHORT_TREATMENT_ID_TO_COLOR_MAP.get( + _standardize_short_treatment_id(short_treatment_id), np.nan + ) def get_properties( module: Union[Module, Type[Module]], @@ -20,8 +82,9 @@ def get_properties( :param module: ``Module`` subclass to get properties for. :return: Set of ``Module`` subclass names corresponding to properties of ``module``. """ - return module.PROPERTIES - + if hasattr(module, 'PROPERTIES'): + return module.PROPERTIES + return None def check_properties_in_module(module: Any, properties: Set[str]) -> Set[str]: """Check if any of the properties are used in the given module's script.""" @@ -42,7 +105,6 @@ def is_valid_tlo_module_subclass(obj: Any, excluded_modules: Set[str]) -> bool: """Check if the object is a valid TLO Module subclass.""" return isinstance(obj, type) and issubclass(obj, Module) and obj.__name__ not in excluded_modules - def get_module_property_map(excluded_modules: Set[str]) -> Mapping[str, Set[Type[Module]]]: """Constructs a map from property names to sets of Module subclass objects. @@ -54,70 +116,173 @@ def get_module_property_map(excluded_modules: Set[str]) -> Mapping[str, Set[Type :raises RuntimeError: Raised if multiple Module subclasses with the same name are defined (and not included in the excluded_modules set). """ + properties_dictionary = {} methods_package_path = os.path.dirname(inspect.getfile(tlo.methods)) - module_property_map: Dict[str, Set[Type[Module]]] = {} - for _, methods_module_name, _ in pkgutil.iter_modules([methods_package_path]): - methods_module = importlib.import_module(f'tlo.methods.{methods_module_name}') + for _, main_module_name, _ in pkgutil.iter_modules([methods_package_path]): + methods_module = importlib.import_module(f'tlo.methods.{main_module_name}') for _, obj in inspect.getmembers(methods_module): if is_valid_tlo_module_subclass(obj, excluded_modules): - properties = get_properties(obj) - - for prop in properties: - if prop not in module_property_map: - module_property_map[prop] = set() - module_property_map[prop].add(obj) - - return module_property_map - - -# Get properties from your target Module subclass -module_properties = get_properties(Hiv) - -# Use the function to find used properties -used_properties = check_properties_in_module(Tb, module_properties) - -# Print the results -if used_properties: - print("The following properties are used in the other module's script:") - for prop in used_properties: - print(f"- {prop}") -else: - print("No properties are used in the other module's script.") - -# Example usage of get_module_property_map -excluded = {'SomeExcludedModule'} # Specify any excluded modules -property_map = get_module_property_map(excluded) - - -def get_module_property_map(excluded_modules: Set[str]) -> Mapping[str, Set[Type[Module]]]: - """Constructs a map from property names to sets of Module subclass objects. - - :param excluded_modules: Set of Module subclass names to exclude from the map. - - :return: A mapping from property names to sets of corresponding Module subclass objects. - This adds an implicit requirement that the names of all the Module subclasses are unique. - - :raises RuntimeError: Raised if multiple Module subclasses with the same name are defined - (and not included in the excluded_modules set). + properties_dictionary[obj.__name__] = obj + return properties_dictionary + +def construct_property_dependency_graph( + excluded_modules: Set[str], + disease_module_node_defaults: dict, + other_module_node_defaults: dict, + pregnancy_related_module_node_defaults: dict, + cancer_related_module_node_defaults: dict, + properies_node_defaults: dict, + get_dependencies: DependencyGetter = get_properties, +): + """Construct a pydot object representing module dependency graph. + :param excluded_modules: Set of ``Module`` subclass names to not included in graph. + :param get_dependencies: Function which given a module gets the set of property + dependencies. Defaults to extracting all dependencies. + :return: Pydot directed graph representing module dependencies. """ - methods_package_path = os.path.dirname(inspect.getfile(tlo.methods)) - module_property_map: Dict[str, Set[Type[Module]]] = {} - - for _, methods_module_name, _ in pkgutil.iter_modules([methods_package_path]): - methods_module = importlib.import_module(f'tlo.methods.{methods_module_name}') - for _, obj in inspect.getmembers(methods_module): - if is_valid_tlo_module_subclass(obj, excluded_modules): - properties = get_properties(obj) - - for prop in properties: - if prop not in module_property_map: - module_property_map[prop] = set() - module_property_map[prop].add(obj) - - return module_property_map - - -# Print the property map for verification -for prop, modules in property_map.items(): - print(f"Property '{prop}' is provided by the following modules: {', '.join(module.__name__ for module in modules)}") + if pydot is None: + raise RuntimeError("pydot package must be installed") + + property_class_map = get_module_property_map(excluded_modules) + property_graph = pydot.Dot("properties", graph_type="digraph", rankdir='LR') + + cancer_module_names = [ + 'BladderCancer', 'BreastCancer', 'OtherAdultCancer', + 'OesophagealCancer', 'ProstateCancer' + ] + + pregnancy_module_names = [ + 'Contraception', 'Labour', 'PregnancySupervisor', + 'PostnatalSupervisor', 'NewbornOutcomes', 'CareOfWomenDuringPregnancy' + ] + + + # Subgraphs for different groups of modules - attempt at aesthetics + disease_module_subgraph = pydot.Subgraph("disease_modules") + property_graph.add_subgraph(disease_module_subgraph) + + pregnancy_modules_subgraph = pydot.Subgraph("pregnancy_modules") + property_graph.add_subgraph(pregnancy_modules_subgraph) + + other_module_subgraph = pydot.Subgraph("other_modules") + property_graph.add_subgraph(other_module_subgraph) + + cancer_modules_subgraph = pydot.Subgraph("cancer_modules") + cancer_modules_subgraph.set_rank('same') + property_graph.add_subgraph(cancer_modules_subgraph) + + infectious_diseases_subgraph = pydot.Subgraph("infectious_diseases") + property_graph.add_subgraph(infectious_diseases_subgraph) + + properties_diseases_subgraph = pydot.Subgraph("properties") + property_graph.add_subgraph(properties_diseases_subgraph) + + # Set default styles for nodes + disease_module_node_defaults["style"] = "filled" + other_module_node_defaults["style"] = "filled" + pregnancy_related_module_node_defaults["style"] = "filled" + cancer_related_module_node_defaults["style"] = "filled" + properies_node_defaults["style"] = "filled" + + for name, module_class in property_class_map.items(): # only works for disease modules, not properties + colour = get_color_short_treatment_id(name) + node_attributes = { + "fillcolor": colour, + "color": "black", # Outline color + "fontname": "Arial", + } + + if name in pregnancy_module_names: + node_attributes.update(pregnancy_related_module_node_defaults) + node_attributes["shape"] = "diamond" # Pregnancy modules + pregnancy_modules_subgraph.add_node(pydot.Node(name, **node_attributes)) + + elif name in cancer_module_names: + node_attributes.update(cancer_related_module_node_defaults) + node_attributes["shape"] = "invtrapezium" # Cancer modules + cancer_modules_subgraph.add_node(pydot.Node(name, **node_attributes)) + + elif Metadata.DISEASE_MODULE not in module_class.METADATA: + node_attributes.update(other_module_node_defaults) + node_attributes["shape"] = "ellipse" # Other modules + other_module_subgraph.add_node(pydot.Node(name, **node_attributes)) + else: + node_attributes.update(disease_module_node_defaults) + node_attributes["shape"] = "box" # Disease modules + disease_module_subgraph.add_node(pydot.Node(name, **node_attributes)) + + + + + for key, property_module in property_class_map.items(): + if property_module not in excluded_modules: + properties_of_module = get_dependencies(property_module) + for key, dependent_module in property_class_map.items(): + if property_module != dependent_module: + used_properties = check_properties_in_module(dependent_module, properties_of_module) + for property in used_properties: + if property.startswith("ri"): + node_attributes = { + "fillcolor": "darkorange", + "color": "black", # Outline color + "fontname": "Arial", + } + else: + node_attributes = { + "fillcolor": "white", + "color": "black", # Outline color + "fontname": "Arial", + } + node_attributes.update(properies_node_defaults) + node_attributes["shape"] = "square" + properties_diseases_subgraph.add_node(pydot.Node(property, **node_attributes)) + properties_diseases_subgraph.set_rank('same') + property_graph.add_edge(pydot.Edge(property, key)) + + return property_graph + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "output_file", type=Path, help=( + "Path to output graph to. File extension will determine output format - for example: dot, dia, png, svg" + ) + ) + args = parser.parse_args() + + excluded_modules = { + "Mockitis", + "ChronicSyndrome", + "Skeleton", + "AlriPropertiesOfOtherModules", + "DiarrhoeaPropertiesOfOtherModules", + "DummyHivModule", + "SimplifiedBirths", + "Demography", + "HealthBurden", + "SymptomManager", + "DummyTbModule", + "ImprovedHealthSystemAndCareSeekingScenarioSwitcher", + "HealthSeekingBehaviour", + "HealthSystem", + "Deviance", + "SimplifiedPregnancyAndLabour", + "DummyDisease", + "Module" + } + + module_graph = construct_property_dependency_graph( + excluded_modules, + disease_module_node_defaults={"shape": "box"}, + other_module_node_defaults={"shape": "ellipse"}, + pregnancy_related_module_node_defaults={"shape": "diamond"}, + cancer_related_module_node_defaults={"shape": "invtrapezium"}, + properies_node_defaults={"shape": "square"} + ) + + format = ( + args.output_file.suffix[1:] if args.output_file.suffix else "raw" + ) + module_graph.write(args.output_file, format=format) From 308b5224e1750b1d9ac0fb9fd5f181b0ed1017d0 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson <106470783+RachelMurray-Watson@users.noreply.github.com> Date: Tue, 8 Oct 2024 14:24:51 +0100 Subject: [PATCH 16/28] Delete src/scripts/longterm_projections/property_dependency_graph.py Duplicate graph --- .../property_dependency_graph.py | 114 ------------------ 1 file changed, 114 deletions(-) delete mode 100644 src/scripts/longterm_projections/property_dependency_graph.py diff --git a/src/scripts/longterm_projections/property_dependency_graph.py b/src/scripts/longterm_projections/property_dependency_graph.py deleted file mode 100644 index 8755c45e8d..0000000000 --- a/src/scripts/longterm_projections/property_dependency_graph.py +++ /dev/null @@ -1,114 +0,0 @@ -"""Construct a graph showing dependencies between modules.""" - -import argparse -from pathlib import Path -from types import MappingProxyType -from typing import Any, Dict, Mapping, Set, Type, Union -import numpy as np -import importlib -import inspect -import os -import pkgutil -import pydot - -import tlo.methods -from tlo import Module -from tlo.methods.hiv import Hiv -from tlo.methods.tb import Tb -from tlo.dependencies import DependencyGetter, get_all_dependencies, is_valid_tlo_module_subclass -from tlo.methods import Metadata - -SHORT_TREATMENT_ID_TO_COLOR_MAP = MappingProxyType({ - # Define your color mappings here - '*': 'black', - 'FirstAttendance*': 'darkgrey', - # ... (other mappings) -}) - - -def _standardize_short_treatment_id(short_treatment_id: str) -> str: - return short_treatment_id.replace('_*', '*').rstrip('*') + '*' - - -def get_color_short_treatment_id(short_treatment_id: str) -> str: - """Return the colour assigned to this shorted TREATMENT_ID.""" - return SHORT_TREATMENT_ID_TO_COLOR_MAP.get( - _standardize_short_treatment_id(short_treatment_id), np.nan - ) - - -def get_properties(module: Union[Module, Type[Module]]) -> Set[str]: - """Get the properties for a ``Module`` subclass.""" - return module.PROPERTIES - - -def check_properties_in_module(module: Any, properties: Set[str]) -> Set[str]: - """Check if any of the properties are used in the given module's script.""" - used_properties = set() - source_code = inspect.getsource(module) - - for prop in properties: - if prop in source_code: - used_properties.add(prop) - - return used_properties - - -def get_module_property_map(excluded_modules: Set[str]) -> Mapping[str, Set[Type[Module]]]: - """Constructs a map from property names to sets of Module subclass objects.""" - methods_package_path = os.path.dirname(inspect.getfile(tlo.methods)) - module_property_map: Dict[str, Set[Type[Module]]] = {} - - for _, methods_module_name, _ in pkgutil.iter_modules([methods_package_path]): - methods_module = importlib.import_module(f'tlo.methods.{methods_module_name}') - for _, obj in inspect.getmembers(methods_module): - if is_valid_tlo_module_subclass(obj, excluded_modules): - properties = get_properties(obj) - for prop in properties: - if prop not in module_property_map: - module_property_map[prop] = set() - module_property_map[prop].add(obj) - - return module_property_map - - -def construct_module_dependency_graph( - excluded_modules: Set[str], - get_dependencies: DependencyGetter = get_all_dependencies, -): - """Construct a pydot object representing the module dependency graph.""" - if pydot is None: - raise RuntimeError("pydot package must be installed") - - module_class_map = get_module_property_map(excluded_modules) - module_graph = pydot.Dot("modules", graph_type="digraph", rankdir='LR') - - for key, module in module_class_map.items(): - for dependency in get_properties(module): - if dependency not in excluded_modules: - module_graph.add_edge(pydot.Edge(dependency, key)) - - return module_graph - - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument( - "output_file", type=Path, help=( - "Path to output graph to. File extension will determine output format - for example: dot, dia, png, svg" - ) - ) - args = parser.parse_args() - - excluded_modules = { - "Mockitis", - "ChronicSyndrome", - "Skeleton", - } - - module_graph = construct_module_dependency_graph(excluded_modules) - - format = ( - args.output_file.suffix[1:] if args.output_file.suffix else "raw" - ) - module_graph.write(args.output_file, format=format) From ab3b3b63c0dd506dc36ab63c18adb376cdc39957 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Tue, 8 Oct 2024 14:29:35 +0100 Subject: [PATCH 17/28] isort --- src/scripts/get_properties/properties_graph.py | 18 +++++++----------- .../property_dependency_graph.py | 13 +++++++------ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py index 162627bb99..1478fd747b 100644 --- a/src/scripts/get_properties/properties_graph.py +++ b/src/scripts/get_properties/properties_graph.py @@ -4,18 +4,18 @@ import importlib import inspect import os +import pkgutil from pathlib import Path from types import MappingProxyType from typing import Any, Callable, Generator, Iterable, Mapping, Optional, Set, Type, Union import numpy as np -import pkgutil import pydot import tlo.methods from tlo import Module -from tlo.methods import Metadata from tlo.dependencies import DependencyGetter, get_all_dependencies, is_valid_tlo_module_subclass +from tlo.methods import Metadata from tlo.methods.hiv import Hiv from tlo.methods.tb import Tb @@ -61,6 +61,7 @@ 'Lifestyle*': 'silver', }) + def _standardize_short_treatment_id(short_treatment_id): return short_treatment_id.replace('_*', '*').rstrip('*') + '*' @@ -74,6 +75,7 @@ def get_color_short_treatment_id(short_treatment_id: str) -> str: _standardize_short_treatment_id(short_treatment_id), np.nan ) + def get_properties( module: Union[Module, Type[Module]], ) -> Set[str]: @@ -86,6 +88,7 @@ def get_properties( return module.PROPERTIES return None + def check_properties_in_module(module: Any, properties: Set[str]) -> Set[str]: """Check if any of the properties are used in the given module's script.""" used_properties = set() @@ -101,10 +104,6 @@ def check_properties_in_module(module: Any, properties: Set[str]) -> Set[str]: return used_properties -def is_valid_tlo_module_subclass(obj: Any, excluded_modules: Set[str]) -> bool: - """Check if the object is a valid TLO Module subclass.""" - return isinstance(obj, type) and issubclass(obj, Module) and obj.__name__ not in excluded_modules - def get_module_property_map(excluded_modules: Set[str]) -> Mapping[str, Set[Type[Module]]]: """Constructs a map from property names to sets of Module subclass objects. @@ -126,6 +125,7 @@ def get_module_property_map(excluded_modules: Set[str]) -> Mapping[str, Set[Type properties_dictionary[obj.__name__] = obj return properties_dictionary + def construct_property_dependency_graph( excluded_modules: Set[str], disease_module_node_defaults: dict, @@ -157,7 +157,6 @@ def construct_property_dependency_graph( 'PostnatalSupervisor', 'NewbornOutcomes', 'CareOfWomenDuringPregnancy' ] - # Subgraphs for different groups of modules - attempt at aesthetics disease_module_subgraph = pydot.Subgraph("disease_modules") property_graph.add_subgraph(disease_module_subgraph) @@ -185,7 +184,7 @@ def construct_property_dependency_graph( cancer_related_module_node_defaults["style"] = "filled" properies_node_defaults["style"] = "filled" - for name, module_class in property_class_map.items(): # only works for disease modules, not properties + for name, module_class in property_class_map.items(): # only works for disease modules, not properties colour = get_color_short_treatment_id(name) node_attributes = { "fillcolor": colour, @@ -212,9 +211,6 @@ def construct_property_dependency_graph( node_attributes["shape"] = "box" # Disease modules disease_module_subgraph.add_node(pydot.Node(name, **node_attributes)) - - - for key, property_module in property_class_map.items(): if property_module not in excluded_modules: properties_of_module = get_dependencies(property_module) diff --git a/src/scripts/longterm_projections/property_dependency_graph.py b/src/scripts/longterm_projections/property_dependency_graph.py index 8755c45e8d..dd7a9b8f76 100644 --- a/src/scripts/longterm_projections/property_dependency_graph.py +++ b/src/scripts/longterm_projections/property_dependency_graph.py @@ -1,22 +1,23 @@ """Construct a graph showing dependencies between modules.""" import argparse -from pathlib import Path -from types import MappingProxyType -from typing import Any, Dict, Mapping, Set, Type, Union -import numpy as np import importlib import inspect import os import pkgutil +from pathlib import Path +from types import MappingProxyType +from typing import Any, Dict, Mapping, Set, Type, Union + +import numpy as np import pydot import tlo.methods from tlo import Module -from tlo.methods.hiv import Hiv -from tlo.methods.tb import Tb from tlo.dependencies import DependencyGetter, get_all_dependencies, is_valid_tlo_module_subclass from tlo.methods import Metadata +from tlo.methods.hiv import Hiv +from tlo.methods.tb import Tb SHORT_TREATMENT_ID_TO_COLOR_MAP = MappingProxyType({ # Define your color mappings here From 091124774f4b11b5742a3d5d26c99946c19d1bed Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Tue, 8 Oct 2024 14:38:19 +0100 Subject: [PATCH 18/28] isort --- src/scripts/get_properties/properties_graph.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py index 1478fd747b..5968a836e6 100644 --- a/src/scripts/get_properties/properties_graph.py +++ b/src/scripts/get_properties/properties_graph.py @@ -1,4 +1,4 @@ -"""Construct a graph showing dependencies between modules.""" +"""Construct a graph showing the property dependency between modules.""" import argparse import importlib @@ -7,22 +7,20 @@ import pkgutil from pathlib import Path from types import MappingProxyType -from typing import Any, Callable, Generator, Iterable, Mapping, Optional, Set, Type, Union +from typing import Any, Mapping, Set, Type, Union import numpy as np -import pydot import tlo.methods from tlo import Module -from tlo.dependencies import DependencyGetter, get_all_dependencies, is_valid_tlo_module_subclass +from tlo.dependencies import DependencyGetter, is_valid_tlo_module_subclass from tlo.methods import Metadata -from tlo.methods.hiv import Hiv -from tlo.methods.tb import Tb try: import pydot except ImportError: pydot = None + SHORT_TREATMENT_ID_TO_COLOR_MAP = MappingProxyType({ '*': 'black', 'FirstAttendance*': 'darkgrey', From dc67a0e02465dc0189db5ed183a13e83ca59a85c Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Tue, 8 Oct 2024 15:00:03 +0100 Subject: [PATCH 19/28] Changed names of functions to avoid overlap --- src/scripts/get_properties/properties_graph.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py index 5968a836e6..42c8c04345 100644 --- a/src/scripts/get_properties/properties_graph.py +++ b/src/scripts/get_properties/properties_graph.py @@ -13,6 +13,7 @@ import tlo.methods from tlo import Module +from tlo.analysis.utils import _standardize_short_treatment_id from tlo.dependencies import DependencyGetter, is_valid_tlo_module_subclass from tlo.methods import Metadata @@ -21,7 +22,7 @@ except ImportError: pydot = None -SHORT_TREATMENT_ID_TO_COLOR_MAP = MappingProxyType({ +SHORT_TREATMENT_ID_TO_COLOR_MAP_EXTRA = MappingProxyType({ '*': 'black', 'FirstAttendance*': 'darkgrey', 'Inpatient*': 'silver', @@ -60,16 +61,12 @@ }) -def _standardize_short_treatment_id(short_treatment_id): - return short_treatment_id.replace('_*', '*').rstrip('*') + '*' - - -def get_color_short_treatment_id(short_treatment_id: str) -> str: - """Return the colour assigned to this shorted TREATMENT_ID. +def get_color_short_treatment_id_extra_modules(short_treatment_id: str) -> str: + """Return the colour (as matplotlib string) assigned to this shorted TREATMENT_ID. Returns `np.nan` if treatment_id is not recognised. """ - return SHORT_TREATMENT_ID_TO_COLOR_MAP.get( + return SHORT_TREATMENT_ID_TO_COLOR_MAP_EXTRA.get( _standardize_short_treatment_id(short_treatment_id), np.nan ) @@ -183,7 +180,7 @@ def construct_property_dependency_graph( properies_node_defaults["style"] = "filled" for name, module_class in property_class_map.items(): # only works for disease modules, not properties - colour = get_color_short_treatment_id(name) + colour = get_color_short_treatment_id_extra_modules(name) node_attributes = { "fillcolor": colour, "color": "black", # Outline color From 4f8cb2daf58edb98d02742cc88ae6764f8e99cfd Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Tue, 8 Oct 2024 15:01:31 +0100 Subject: [PATCH 20/28] Updated docstring --- src/scripts/get_properties/properties_graph.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py index 42c8c04345..9f5b518a71 100644 --- a/src/scripts/get_properties/properties_graph.py +++ b/src/scripts/get_properties/properties_graph.py @@ -134,6 +134,7 @@ def construct_property_dependency_graph( :param excluded_modules: Set of ``Module`` subclass names to not included in graph. :param get_dependencies: Function which given a module gets the set of property dependencies. Defaults to extracting all dependencies. + :param X_node_defaults: Defaults for specified subgraphs. :return: Pydot directed graph representing module dependencies. """ if pydot is None: From 7f4d38fef6df5e60ff639605356557ac3c6224d6 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Tue, 8 Oct 2024 15:27:35 +0100 Subject: [PATCH 21/28] Initial attempt at creating module-level property graphs --- .../get_properties/properties_graph.py | 44 ++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py index 9f5b518a71..01c46dd3a8 100644 --- a/src/scripts/get_properties/properties_graph.py +++ b/src/scripts/get_properties/properties_graph.py @@ -210,7 +210,7 @@ def construct_property_dependency_graph( for key, property_module in property_class_map.items(): if property_module not in excluded_modules: properties_of_module = get_dependencies(property_module) - for key, dependent_module in property_class_map.items(): + for module, dependent_module in property_class_map.items(): if property_module != dependent_module: used_properties = check_properties_in_module(dependent_module, properties_of_module) for property in used_properties: @@ -235,6 +235,44 @@ def construct_property_dependency_graph( return property_graph +def property_dependency_map_by_module( + excluded_modules: Set[str], + properies_node_defaults: dict, + output_path: Path, + get_dependencies: DependencyGetter = get_properties, +): + """ + param excluded_modules: modules for which dependencies should not be checked + param properies_node_defaults: default properies of a node + param output_path: where files write to + param get_dependencies: Function which given a module gets the set of property + dependencies. Defaults to extracting all dependencies. + """ + property_class_map = get_module_property_map(excluded_modules) + + for key, property_module in property_class_map.items(): + colour = get_color_short_treatment_id_extra_modules(key) + node_attributes = { + "fillcolor": colour, + "color": "black", # Outline color + "fontname": "Arial", + } + if property_module not in excluded_modules: + properties_of_module = get_dependencies(property_module) + property_graph = pydot.Dot("properties", graph_type="digraph", rankdir='LR') + for module, dependent_module in property_class_map.items(): + if property_module != dependent_module: + used_properties = check_properties_in_module(dependent_module, properties_of_module) + for property in used_properties: + node_attributes.update(properies_node_defaults) + node_attributes["shape"] = "square" + property_graph.add_node(pydot.Node(property, **node_attributes)) + property_graph.add_edge(pydot.Edge(property, key)) + graph_name = output_path/f"{key}.png" + print(graph_name) + property_graph.write(graph_name) + + if __name__ == "__main__": parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( @@ -264,6 +302,8 @@ def construct_property_dependency_graph( "DummyDisease", "Module" } + property_dependency_map_by_module(excluded_modules, properies_node_defaults={"shape": "square"}, + output_path=args.output_file) module_graph = construct_property_dependency_graph( excluded_modules, @@ -277,4 +317,4 @@ def construct_property_dependency_graph( format = ( args.output_file.suffix[1:] if args.output_file.suffix else "raw" ) - module_graph.write(args.output_file, format=format) + module_graph.write(args.output_file/"property_graph.png", format=format) From fc49e88a0a5c588031354196b495a335455222c3 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Tue, 8 Oct 2024 15:36:12 +0100 Subject: [PATCH 22/28] Fixed typo --- src/scripts/get_properties/properties_graph.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py index 01c46dd3a8..b2b9d1a820 100644 --- a/src/scripts/get_properties/properties_graph.py +++ b/src/scripts/get_properties/properties_graph.py @@ -210,7 +210,7 @@ def construct_property_dependency_graph( for key, property_module in property_class_map.items(): if property_module not in excluded_modules: properties_of_module = get_dependencies(property_module) - for module, dependent_module in property_class_map.items(): + for main_module, dependent_module in property_class_map.items(): if property_module != dependent_module: used_properties = check_properties_in_module(dependent_module, properties_of_module) for property in used_properties: @@ -230,7 +230,7 @@ def construct_property_dependency_graph( node_attributes["shape"] = "square" properties_diseases_subgraph.add_node(pydot.Node(property, **node_attributes)) properties_diseases_subgraph.set_rank('same') - property_graph.add_edge(pydot.Edge(property, key)) + property_graph.add_edge(pydot.Edge(property, main_module)) return property_graph @@ -260,7 +260,7 @@ def property_dependency_map_by_module( if property_module not in excluded_modules: properties_of_module = get_dependencies(property_module) property_graph = pydot.Dot("properties", graph_type="digraph", rankdir='LR') - for module, dependent_module in property_class_map.items(): + for key, dependent_module in property_class_map.items(): if property_module != dependent_module: used_properties = check_properties_in_module(dependent_module, properties_of_module) for property in used_properties: @@ -269,7 +269,7 @@ def property_dependency_map_by_module( property_graph.add_node(pydot.Node(property, **node_attributes)) property_graph.add_edge(pydot.Edge(property, key)) graph_name = output_path/f"{key}.png" - print(graph_name) + print(property_graph) property_graph.write(graph_name) @@ -302,8 +302,8 @@ def property_dependency_map_by_module( "DummyDisease", "Module" } - property_dependency_map_by_module(excluded_modules, properies_node_defaults={"shape": "square"}, - output_path=args.output_file) + # property_dependency_map_by_module(excluded_modules, properies_node_defaults={"shape": "square"}, + # output_path=args.output_file) module_graph = construct_property_dependency_graph( excluded_modules, @@ -317,4 +317,4 @@ def property_dependency_map_by_module( format = ( args.output_file.suffix[1:] if args.output_file.suffix else "raw" ) - module_graph.write(args.output_file/"property_graph.png", format=format) + module_graph.write(args.output_file, format=format) From 31a61a0933f4209926fb149aea072c66cb432f74 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Tue, 8 Oct 2024 15:57:26 +0100 Subject: [PATCH 23/28] Graphs for each individual module --- .../get_properties/properties_graph.py | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py index b2b9d1a820..e8b2ec8541 100644 --- a/src/scripts/get_properties/properties_graph.py +++ b/src/scripts/get_properties/properties_graph.py @@ -250,27 +250,27 @@ def property_dependency_map_by_module( """ property_class_map = get_module_property_map(excluded_modules) - for key, property_module in property_class_map.items(): + for key, dependent_module in property_class_map.items(): colour = get_color_short_treatment_id_extra_modules(key) node_attributes = { "fillcolor": colour, "color": "black", # Outline color "fontname": "Arial", + "shape": "square", } - if property_module not in excluded_modules: - properties_of_module = get_dependencies(property_module) + if dependent_module not in excluded_modules: property_graph = pydot.Dot("properties", graph_type="digraph", rankdir='LR') - for key, dependent_module in property_class_map.items(): - if property_module != dependent_module: + for property_key, property_module in property_class_map.items(): + if property_module != dependent_module and property_module not in excluded_modules: + properties_of_module = get_dependencies(property_module) used_properties = check_properties_in_module(dependent_module, properties_of_module) for property in used_properties: - node_attributes.update(properies_node_defaults) - node_attributes["shape"] = "square" property_graph.add_node(pydot.Node(property, **node_attributes)) property_graph.add_edge(pydot.Edge(property, key)) + graph_name = output_path/f"{key}.png" print(property_graph) - property_graph.write(graph_name) + property_graph.write(graph_name, format="png") if __name__ == "__main__": @@ -302,8 +302,8 @@ def property_dependency_map_by_module( "DummyDisease", "Module" } - # property_dependency_map_by_module(excluded_modules, properies_node_defaults={"shape": "square"}, - # output_path=args.output_file) + property_dependency_map_by_module(excluded_modules, properies_node_defaults={"shape": "square"}, + output_path=args.output_file) module_graph = construct_property_dependency_graph( excluded_modules, @@ -314,7 +314,4 @@ def property_dependency_map_by_module( properies_node_defaults={"shape": "square"} ) - format = ( - args.output_file.suffix[1:] if args.output_file.suffix else "raw" - ) - module_graph.write(args.output_file, format=format) + module_graph.write(args.output_file/"property_graph_full.png", format="png") From 3b94583f73292e155e09aca2e4230d524e89493b Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Tue, 8 Oct 2024 16:21:28 +0100 Subject: [PATCH 24/28] Aesthetics - still needs colour --- .../get_properties/properties_graph.py | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py index e8b2ec8541..161cf98f74 100644 --- a/src/scripts/get_properties/properties_graph.py +++ b/src/scripts/get_properties/properties_graph.py @@ -249,27 +249,39 @@ def property_dependency_map_by_module( dependencies. Defaults to extracting all dependencies. """ property_class_map = get_module_property_map(excluded_modules) - + property_node_attributes = { + "fillcolor": "white", + "color": "black", # Outline color + "fontname": "Arial", + "shape": "square", + } + node_attributes = { + "color": "black", # Outline color + "fontname": "Arial", + "shape": "square", + } for key, dependent_module in property_class_map.items(): - colour = get_color_short_treatment_id_extra_modules(key) - node_attributes = { - "fillcolor": colour, - "color": "black", # Outline color - "fontname": "Arial", - "shape": "square", - } if dependent_module not in excluded_modules: + colour = get_color_short_treatment_id_extra_modules(key) + node_attributes = { + "fillcolor": colour, + "color": "black", + "fontname": "Arial", + "shape": "square", + } property_graph = pydot.Dot("properties", graph_type="digraph", rankdir='LR') + print(node_attributes) + property_graph.add_node(pydot.Node(key, fillcolor=***node_attributes)) for property_key, property_module in property_class_map.items(): - if property_module != dependent_module and property_module not in excluded_modules: + if key != property_key and property_module not in excluded_modules: properties_of_module = get_dependencies(property_module) used_properties = check_properties_in_module(dependent_module, properties_of_module) for property in used_properties: - property_graph.add_node(pydot.Node(property, **node_attributes)) + property_graph.add_node(pydot.Node(property, **property_node_attributes)) property_graph.add_edge(pydot.Edge(property, key)) graph_name = output_path/f"{key}.png" - print(property_graph) + #print(property_graph) property_graph.write(graph_name, format="png") From b17e1930098c637d5131e25b41862806ccf99eaa Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Wed, 9 Oct 2024 10:32:51 +0100 Subject: [PATCH 25/28] reduced duplication --- src/scripts/get_properties/properties_graph.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/scripts/get_properties/properties_graph.py b/src/scripts/get_properties/properties_graph.py index 161cf98f74..8c79787cc6 100644 --- a/src/scripts/get_properties/properties_graph.py +++ b/src/scripts/get_properties/properties_graph.py @@ -248,18 +248,14 @@ def property_dependency_map_by_module( param get_dependencies: Function which given a module gets the set of property dependencies. Defaults to extracting all dependencies. """ - property_class_map = get_module_property_map(excluded_modules) property_node_attributes = { "fillcolor": "white", - "color": "black", # Outline color - "fontname": "Arial", - "shape": "square", - } - node_attributes = { - "color": "black", # Outline color + "color": "black", "fontname": "Arial", "shape": "square", + "style": "filled", } + property_class_map = get_module_property_map(excluded_modules) for key, dependent_module in property_class_map.items(): if dependent_module not in excluded_modules: colour = get_color_short_treatment_id_extra_modules(key) @@ -268,10 +264,10 @@ def property_dependency_map_by_module( "color": "black", "fontname": "Arial", "shape": "square", + "style": "filled", } property_graph = pydot.Dot("properties", graph_type="digraph", rankdir='LR') - print(node_attributes) - property_graph.add_node(pydot.Node(key, fillcolor=***node_attributes)) + property_graph.add_node(pydot.Node(key, **node_attributes)) for property_key, property_module in property_class_map.items(): if key != property_key and property_module not in excluded_modules: properties_of_module = get_dependencies(property_module) From 987152f70278578afbf4b508bd15b1d97ef7318f Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Thu, 17 Oct 2024 11:01:52 +0100 Subject: [PATCH 26/28] Removed all additional dependencies --- .../methods/care_of_women_during_pregnancy.py | 58 +++++++++++-------- src/tlo/methods/hiv.py | 2 +- src/tlo/methods/labour.py | 3 +- src/tlo/methods/measles.py | 2 - src/tlo/methods/newborn_outcomes.py | 3 +- src/tlo/methods/postnatal_supervisor.py | 3 +- src/tlo/methods/tb.py | 2 - 7 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 27b2b7bf3d..69ce038299 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -59,7 +59,7 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'PregnancySupervisor'} - ADDITIONAL_DEPENDENCIES = {'Contraception', 'Labour', 'Lifestyle', 'Hiv', 'Epi', 'Depression', 'Malaria'} + ADDITIONAL_DEPENDENCIES = {'Contraception', 'Labour', 'Lifestyle'} METADATA = { Metadata.USES_HEALTHSYSTEM, @@ -307,7 +307,7 @@ def get_and_store_pregnancy_item_codes(self): self.item_codes_preg_consumables['syphilis_test'] = {ic('Test, Rapid plasma reagin (RPR)'): 1} # ------------------------------------------- SYPHILIS TREATMENT ---------------------------------------------- - self.item_codes_preg_consumables['syphilis_treatment'] = \ + self.item_codes_preg_consumables['syphilis_treatment'] =\ {ic('Benzathine benzylpenicillin, powder for injection, 2.4 million IU'): 1} # ----------------------------------------------- GDM TEST ---------------------------------------------------- @@ -395,6 +395,7 @@ def initialise_simulation(self, sim): sensitivity=params['sensitivity_urine_protein_1_plus'], specificity=params['specificity_urine_protein_1_plus']), + # This test represents point of care haemoglobin testing used in ANC to detect anaemia (all-severity) point_of_care_hb_test=DxTest( property='ps_anaemia_in_pregnancy', @@ -610,7 +611,7 @@ def calculate_visit_date_and_schedule_visit(visit): # We subtract this woman's current gestational age from the recommended gestational age for the next # contact weeks_due_next_visit = int(recommended_gestation_next_anc - df.at[individual_id, - 'ps_gestational_age_in_weeks']) + 'ps_gestational_age_in_weeks']) # And use this value as the number of weeks until she is required to return for her next ANC visit_date = self.sim.date + DateOffset(weeks=weeks_due_next_visit) @@ -738,7 +739,8 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # 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): + 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'}) @@ -752,9 +754,10 @@ def screening_interventions_delivered_at_every_contact(self, 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 \ + 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) @@ -764,9 +767,10 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # 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: - if (df.at[person_id, 'ps_htn_disorders'] == 'severe_pre_eclamp') or \ + if (df.at[person_id, 'ps_htn_disorders'] == 'severe_pre_eclamp') or\ (df.at[person_id, 'ps_htn_disorders'] == 'eclampsia') or \ - not df.at[person_id, 'ac_gest_htn_on_treatment']: + not df.at[person_id, 'ac_gest_htn_on_treatment']: + df.at[person_id, 'ac_to_be_admitted'] = True # Here we conduct screening and initiate treatment for depression as needed @@ -789,7 +793,7 @@ def iron_and_folic_acid_supplementation(self, hsi_event): # check consumable availability - dose is total days of pregnancy x 2 tablets 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()} + 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) @@ -805,6 +809,7 @@ def iron_and_folic_acid_supplementation(self, hsi_event): # Women started on IFA at this stage may already be anaemic, we here apply a probability that # starting on a course of IFA will correct anaemia prior to follow up if self.rng.random_sample() < params['effect_of_ifa_for_resolving_anaemia']: + # Store date of resolution for daly calculations pregnancy_helper_functions.store_dalys_in_mni( person_id, mni, f'{df.at[person_id, "ps_anaemia_in_pregnancy"]}_anaemia_resolution', @@ -824,7 +829,7 @@ def balance_energy_and_protein_supplementation(self, hsi_event): # If the consumables are available... days = self.get_approx_days_of_pregnancy(person_id) - updated_cons = {k: v * days for (k, v) in + 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( @@ -869,6 +874,7 @@ def tetanus_vaccination(self, hsi_event): person_id = hsi_event.target if 'Epi' in self.sim.modules: + # Define the HSI in which the vaccine is delivered vaccine_hsi = HSI_TdVaccine(self.sim.modules['Epi'], person_id=person_id, suppress_footprint=True) @@ -997,7 +1003,7 @@ def syphilis_screening_and_treatment(self, hsi_event): 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) + 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: @@ -1064,7 +1070,7 @@ def gdm_screening(self, hsi_event): # We check if this woman has any of the key risk factors, if so they are sent for additional blood tests 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']: + 'ps_prev_stillbirth']: # If they are available, the test is conducted if self.rng.random_sample() < params['prob_intervention_delivered_gdm_test']: @@ -1089,6 +1095,7 @@ def gdm_screening(self, hsi_event): # 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) @@ -1133,12 +1140,13 @@ def check_anc1_can_run(self, individual_id, gest_age_next_contact): (date_difference > pd.to_timedelta(7, unit='D')) or (df.at[individual_id, 'ac_total_anc_visits_current_pregnancy'] > 0) or (df.at[individual_id, 'ps_gestational_age_in_weeks'] < 7) - ): + ): return False # If the woman is an inpatient when ANC1 is scheduled, she will try and return at the next appropriate # gestational age if df.at[individual_id, 'hs_is_inpatient']: + # We assume that she will return for her first appointment at the next gestation in the schedule logger.debug(key='message', data=f'mother {individual_id} is scheduled to attend ANC today but is ' f'currently an inpatient- she will be scheduled to arrive at her next ' @@ -1213,7 +1221,7 @@ def full_blood_count_testing(self, hsi_event): 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) + dx_tests_to_run='full_blood_count_hb', hsi_event=hsi_event) if test_result and (df.at[person_id, 'ps_anaemia_in_pregnancy'] == 'none'): return 'non_severe' @@ -1314,7 +1322,7 @@ def initiate_treatment_for_severe_hypertension(self, individual_id, hsi_event): # disease (as the disease is multi-system and hypertension is only one contributing factor to mortality) but # instead use this property to reduce risk of acute death from this episode of disease if (df.at[individual_id, 'ps_htn_disorders'] == 'severe_pre_eclamp') or (df.at[individual_id, - 'ps_htn_disorders'] == + 'ps_htn_disorders'] == 'eclampsia'): df.at[individual_id, 'ac_iv_anti_htn_treatment'] = True @@ -1408,7 +1416,7 @@ def calculate_beddays(self, individual_id): # they have reached that gestation elif ((mother.ps_placenta_praevia and (mother.ps_antepartum_haemorrhage == 'mild_moderate')) or (mother.ps_premature_rupture_of_membranes and not mother.ps_chorioamnionitis)) and \ - (mother.ps_gestational_age_in_weeks < 37): + (mother.ps_gestational_age_in_weeks < 37): beddays = int((37 * 7) - (mother.ps_gestational_age_in_weeks * 7)) else: @@ -1849,7 +1857,7 @@ def apply(self, person_id, squeeze_factor): # =================================== INTERVENTIONS ==================================================== self.add_equipment({'Weighing scale', 'Measuring tapes', - 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 'Stethoscope, foetal, monaural, Pinard, plastic'}) self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) @@ -1994,7 +2002,6 @@ class HSI_CareOfWomenDuringPregnancy_FocusedANCVisit(HSI_Event, IndividualScopeE within some analyses as the scheduled of interventions per visit is different from the ANC8 structure. This event represents all four ANC visits. """ - def __init__(self, module, person_id, visit_number): super().__init__(module, person_id=person_id) assert isinstance(module, CareOfWomenDuringPregnancy) @@ -2090,6 +2097,7 @@ def apply(self, person_id, squeeze_factor): # Following this the woman's next visit is scheduled (if she hasn't already attended 4 visits) if self.visit_number < 4: + # update the visit number for the event scheduling self.visit_number = self.visit_number + 1 @@ -2123,7 +2131,7 @@ def apply(self, person_id, squeeze_factor): # If the woman is no longer alive, pregnant is in labour or is an inpatient already then the event doesnt run if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'is_pregnant'] or \ - df.at[person_id, 'la_currently_in_labour'] or df.at[person_id, 'hs_is_inpatient']: + df.at[person_id, 'la_currently_in_labour'] or df.at[person_id, 'hs_is_inpatient']: return # We set this admission property to show shes being admitted for induction of labour and hand her over to the @@ -2211,6 +2219,7 @@ def apply(self, person_id, squeeze_factor): follow_up_date = self.sim.date + DateOffset(days=28) if pd.isnull(mother.ac_date_next_contact) or ((mother.ac_date_next_contact - self.sim.date) > pd.to_timedelta(28, unit='D')): + outpatient_checkup = HSI_CareOfWomenDuringPregnancy_AntenatalOutpatientManagementOfAnaemia( self.sim.modules['CareOfWomenDuringPregnancy'], person_id=person_id) @@ -2281,7 +2290,7 @@ def apply(self, person_id, squeeze_factor): # to determine mode of delivery here if mother.ps_htn_disorders == 'eclampsia': df.at[person_id, 'ac_admitted_for_immediate_delivery'] = self.module.rng.choice( - delivery_mode, p=params['prob_delivery_modes_ec']) + delivery_mode, p=params['prob_delivery_modes_ec']) elif mother.ps_htn_disorders == 'severe_pre_eclamp': df.at[person_id, 'ac_admitted_for_immediate_delivery'] = self.module.rng.choice( @@ -2315,7 +2324,7 @@ def apply(self, person_id, squeeze_factor): # bleed and her current gestation at the time of bleeding if ((mother.ps_antepartum_haemorrhage == 'severe') and mother.ps_gestational_age_in_weeks >= 28) or \ - (mother.ps_gestational_age_in_weeks >= 37): + (mother.ps_gestational_age_in_weeks >= 37): # Women experiencing severe bleeding are admitted immediately for caesarean section df.at[person_id, 'ac_admitted_for_immediate_delivery'] = 'caesarean_now' mni[person_id]['cs_indication'] = 'an_aph_pp' @@ -2434,6 +2443,7 @@ def apply(self, person_id, squeeze_factor): # If she is determined to still be anaemic she is admitted for additional treatment via the inpatient event if fbc_result in ('mild', 'moderate', 'severe'): + admission = HSI_CareOfWomenDuringPregnancy_AntenatalWardInpatientCare( self.sim.modules['CareOfWomenDuringPregnancy'], person_id=person_id) @@ -2458,7 +2468,6 @@ class HSI_CareOfWomenDuringPregnancy_AntenatalOutpatientManagementOfGestationalD pregnancy. This event manages repeat blood testing for women who were found to have GDM and treated. If the woman remains hyperglycaemic she is moved to the next line treatment and scheduled to return for follow up. """ - def __init__(self, module, person_id): super().__init__(module, person_id=person_id) assert isinstance(module, CareOfWomenDuringPregnancy) @@ -2478,7 +2487,7 @@ def apply(self, person_id, squeeze_factor): return if not mother.la_currently_in_labour and not mother.hs_is_inpatient and mother.ps_gest_diab != 'none' \ - and (mother.ac_gest_diab_on_treatment != 'none') and (mother.ps_gestational_age_in_weeks > 21): + and (mother.ac_gest_diab_on_treatment != 'none') and (mother.ps_gestational_age_in_weeks > 21): est_length_preg = self.module.get_approx_days_of_pregnancy(person_id) @@ -2511,7 +2520,7 @@ def schedule_gdm_event_and_checkup(): 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) + self.module, self, cons=updated_cons, opt_cons=None) # If the meds are available women are started on that treatment if avail: @@ -2529,7 +2538,7 @@ def schedule_gdm_event_and_checkup(): # Dose is (avg.) 0.8 units per KG per day. Average weight is an appoximation required_units_per_preg = 65 * (0.8 * est_length_preg) - required_vials = np.ceil(required_units_per_preg / 1000) + required_vials = np.ceil(required_units_per_preg/1000) updated_cons = {k: v * required_vials for (k, v) in self.module.item_codes_preg_consumables['insulin_treatment'].items()} @@ -2710,6 +2719,7 @@ def __init__(self, module): super().__init__(module, frequency=DateOffset(months=self.repeat)) def apply(self, population): + yearly_counts = self.module.anc_counter logger.info(key='anc_visits_which_ran', data=yearly_counts) self.module.anc_counter = {1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0} diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index 07b5c6775b..ece2e28c7c 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -81,7 +81,7 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden",} - ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes', 'Schisto', 'CardioMetabolicDisorders'} + ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes'} METADATA = { Metadata.DISEASE_MODULE, diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 48cf1c3eb3..35081b7d27 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -77,7 +77,8 @@ def __init__(self, name=None, resourcefilepath=None): ADDITIONAL_DEPENDENCIES = { 'PostnatalSupervisor', 'CareOfWomenDuringPregnancy', 'Lifestyle', 'PregnancySupervisor', - 'HealthSystem', 'Contraception', 'NewbornOutcomes', 'Depression', 'Hiv' + 'HealthSystem', 'Contraception', + 'NewbornOutcomes', } METADATA = { diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index 020d7d80fc..39f9828860 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -29,8 +29,6 @@ class Measles(Module, GenericFirstAppointmentsMixin): OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden'} - ADDITIONAL_DEPENDENCIES = {'Epi'} - # declare metadata METADATA = { Metadata.DISEASE_MODULE, diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index d6a2455093..3691bc6003 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -49,8 +49,7 @@ def __init__(self, name=None, resourcefilepath=None): 'CareOfWomenDuringPregnancy', 'Labour', 'PostnatalSupervisor', - 'PregnancySupervisor', - 'Hiv' + 'PregnancySupervisor' } METADATA = { diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index dc1d3f2d2e..25bce6013f 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -44,8 +44,7 @@ def __init__(self, name=None, resourcefilepath=None): INIT_DEPENDENCIES = {'Demography', 'HealthSystem'} - ADDITIONAL_DEPENDENCIES = {'Labour', 'Lifestyle', 'NewbornOutcomes', 'PregnancySupervisor', - 'Hiv', 'CareOfWomenDuringPregnancy'} + ADDITIONAL_DEPENDENCIES = {'Labour', 'Lifestyle', 'NewbornOutcomes', 'PregnancySupervisor'} METADATA = {Metadata.DISEASE_MODULE, Metadata.USES_HEALTHSYSTEM, diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 76e5b74d38..623ee2e483 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -58,8 +58,6 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden", "Hiv"} - ADDITIONAL_DEPENDENCIES = {"CardioMetabolicDisorders"} - METADATA = { Metadata.DISEASE_MODULE, Metadata.USES_SYMPTOMMANAGER, From f7bb53db3b38b8f4b09865735d97e131da2b9a7b Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Thu, 17 Oct 2024 11:04:21 +0100 Subject: [PATCH 27/28] typo --- src/tlo/methods/hiv.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index ece2e28c7c..d6455cc861 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -79,7 +79,7 @@ def __init__(self, name=None, resourcefilepath=None, run_with_checks=False): INIT_DEPENDENCIES = {"Demography", "HealthSystem", "Lifestyle", "SymptomManager"} - OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden",} + OPTIONAL_INIT_DEPENDENCIES = {"HealthBurden"} ADDITIONAL_DEPENDENCIES = {'Tb', 'NewbornOutcomes'} From 5363892b91923314741cc536f621cf4ab8a366c4 Mon Sep 17 00:00:00 2001 From: RachelMurray-Watson Date: Thu, 17 Oct 2024 11:29:35 +0100 Subject: [PATCH 28/28] Moved to get_properties folder --- .../get_properties}/module_dependencies_graph.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{tlo/analysis => scripts/get_properties}/module_dependencies_graph.py (100%) diff --git a/src/tlo/analysis/module_dependencies_graph.py b/src/scripts/get_properties/module_dependencies_graph.py similarity index 100% rename from src/tlo/analysis/module_dependencies_graph.py rename to src/scripts/get_properties/module_dependencies_graph.py