From 1b04beddd25bad3a2a921b4310c4ac62e2ab125d Mon Sep 17 00:00:00 2001 From: thewati Date: Mon, 18 Aug 2025 16:01:58 +0200 Subject: [PATCH 01/31] creating chronic kidney disease module for transplants --- src/tlo/methods/cmd_chronic_kidney_disease.py | 244 ++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 src/tlo/methods/cmd_chronic_kidney_disease.py diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py new file mode 100644 index 0000000000..ec3a91150a --- /dev/null +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -0,0 +1,244 @@ +from pathlib import Path +from typing import Union + +import numpy as np +import pandas as pd + +from tlo import DateOffset, Module, Parameter, Population, Property, Simulation, Types, logging +from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent +from tlo.lm import LinearModel, LinearModelType, Predictor +from tlo.methods import Metadata, cardio_metabolic_disorders +from tlo.methods.hsi_event import HSI_Event +from tlo.methods.hsi_generic_first_appts import HSIEventScheduler +from tlo.methods.symptommanager import Symptom +from tlo.population import IndividualProperties + +logger = logging.getLogger(__name__) +logger.setLevel(logging.INFO) + + +class CMDChronicKidneyDisease(Module): + """This is the Chronic Kidney Disease module for kidney transplants.""" + + INIT_DEPENDENCIES = {'SymptomManager', 'Lifestyle', 'HealthSystem', 'CardioMetabolicDisorders'} + ADDITIONAL_DEPENDENCIES = set() + + METADATA = { + Metadata.DISEASE_MODULE, + Metadata.USES_SYMPTOMMANAGER, + Metadata.USES_HEALTHSYSTEM, + Metadata.USES_HEALTHBURDEN, + } + + PARAMETERS = { + "rate_onset_to_stage1-4": Parameter(Types.REAL, + "Probability of people who get diagnosed with CKD stage 1-4"), + "rate_stage1-4_to_stage5": Parameter(Types.REAL, + "Probability of people who get diagnosed with stage 5 (ESRD)"), + "init_prob_any_ckd": Parameter(Types.LIST, "Initial probability of anyone with diabetic retinopathy"), + "rp_ckd_ex_alc": Parameter( + Types.REAL, "relative prevalence at baseline of CKD if excessive alcohol" + ), + "rp_ckd_high_sugar": Parameter( + Types.REAL, "relative prevalence at baseline of CKD if high sugar" + ), + "rp_ckd_low_ex": Parameter( + Types.REAL, "relative prevalence at baseline of CKD if low exercise" + ), + } + + PROPERTIES = { + "ckd_status": Property( + Types.CATEGORICAL, + "CKD status", + categories=["none", "stage1-4", "stage5"], + ), + "ckd_on_treatment": Property( + Types.BOOL, "Whether this person is on CKD treatment", + ), + "ckd_date_treatment": Property( + Types.DATE, + "date of first receiving CKD treatment (pd.NaT if never started treatment)" + ), + "ckd_stage_at_which_treatment_given": Property( + Types.CATEGORICAL, + "The CKD stage at which treatment was given (used to apply stage-specific treatment effect)", + categories=["stage1-4", "stage5", "chronic_dialysis"] + ), + "ckd_diagnosed": Property( + Types.BOOL, "Whether this person has been diagnosed with any CKD" + ), + "ckd_date_diagnosis": Property( + Types.DATE, + "The date of diagnosis of CKD (pd.NaT if never diagnosed)" + ), + "ckd_total_dialysis_sessions": Property(Types.INT, + "total number of dialysis sessions the person has ever had"), + } + + def __init__(self): + super().__init__() + self.cons_item_codes = None # (Will store consumable item codes) + + def read_parameters(self, data_folder: str | Path) -> None: + """ initialise module parameters. Here we are assigning values to all parameters defined at the beginning of + this module. + + :param data_folder: Path to the folder containing parameter values + + """ + # TODO Read from resourcefile + self.parameters['rate_onset_to_stage1-4'] = 0.29 + self.parameters['rate_stage1-4_to_stage5'] = 0.4 + + self.parameters['init_prob_any_ckd'] = [0.5, 0.3, 0.2] + + self.parameters['rp_ckd_ex_alc'] = 1.1 + self.parameters['rp_ckd_high_sugar'] = 1.2 + self.parameters['rp_ckd_low_ex'] = 1.3 + + #todo use chronic_kidney_disease_symptoms from cardio_metabolic_disorders.py + + # self.sim.modules['SymptomManager'].register_symptom( + # Symptom(name='blindness_partial'), + # Symptom(name='blindness_full') + # ) + + def initialise_population(self, population: Population) -> None: + """ Set property values for the initial population + + :param population: all individuals in the model + + """ + df = population.props + # p = self.parameters + + alive_ckd_idx = df.loc[df.is_alive & df.nc_chronic_kidney_disease].index + + # any_dr_idx = alive_diabetes_idx[ + # self.rng.random_sample(size=len(alive_diabetes_idx)) < self.parameters['init_prob_any_dr'] + # ] + # no_dr_idx = set(alive_diabetes_idx) - set(any_dr_idx) + # + # proliferative_dr_idx = any_dr_idx[ + # self.rng.random_sample(size=len(any_dr_idx)) < self.parameters['init_prob_proliferative_dr'] + # ] + # + # mild_dr_idx = set(any_dr_idx) - set(proliferative_dr_idx) + + # write to property: + df.loc[df.is_alive & ~df.nc_chronic_kidney_disease, 'ckd_status'] = 'none' + + df.loc[list(alive_ckd_idx), "ckd_on_treatment"] = False + df.loc[list(alive_ckd_idx), "ckd_diagnosed"] = False + df.loc[list(alive_ckd_idx), "ckd_date_treatment"] = pd.NaT + df.loc[list(alive_ckd_idx), "ckd_stage_at_which_treatment_given"] = "none" + df.loc[list(alive_ckd_idx), "ckd_date_diagnosis"] = pd.NaT + + # -------------------- ckd_status ----------- + # Determine who has CKD at all stages: + # check parameters are sensible: probability of having any CKD stage cannot exceed 1.0 + assert sum(self.parameters['init_prob_any_ckd']) <= 1.0 + + lm_init_ckd_status_any_ckd = LinearModel( + LinearModelType.MULTIPLICATIVE, + sum(self.parameters['init_prob_any_ckd']), + Predictor('li_ex_alc').when(True, self.parameters['rp_ckd_ex_alc']), + Predictor('li_high_sugar').when(True, self.parameters['rp_ckd_high_sugar']), + Predictor('li_low_ex').when(True, self.parameters['rp_ckd_low_ex']), + ) + + # Get boolean Series of who has ckd + has_ckd = lm_init_ckd_status_any_ckd.predict(df.loc[df.is_alive & df.nc_chronic_kidney_disease], self.rng) + + # Get indices of those with DR + ckd_idx = has_ckd[has_ckd].index if has_ckd.any() else pd.Index([]) + + if not ckd_idx.empty: + # Get non-none categories + categories = [cat for cat in df.ckd_status.cat.categories if cat != 'none'] + + # Verify probabilities match categories + assert len(categories) == len(self.parameters['init_prob_any_ckd']) + + # Normalize probabilities + total_prob = sum(self.parameters['init_prob_any_ckd']) + probs = [p / total_prob for p in self.parameters['init_prob_any_ckd']] + + # Assign DR stages + df.loc[ckd_idx, 'ckd_status'] = self.rng.choice( + categories, + size=len(ckd_idx), + p=probs + ) + + def initialise_simulation(self, sim: Simulation) -> None: + """ This is where you should include all things you want to be happening during simulation + * Schedule the main polling event + * Schedule the main logging event + * Call the LinearModels + """ + sim.schedule_event(CMDChronicKidneyDiseasePollEvent(self), date=sim.date) + sim.schedule_event(CMDChronicKidneyDiseaseLoggingEvent(self), sim.date + DateOffset(months=1)) + self.make_the_linear_models() + self.look_up_consumable_item_codes() + + def report_daly_values(self) -> pd.Series: + return pd.Series(index=self.sim.population.props.index, data=0.0) + + def on_birth(self, mother_id: int, child_id: int) -> None: + """ Set properties of a child when they are born. + :param child_id: the new child + """ + self.sim.population.props.at[child_id, 'ckd_status'] = 'none' + self.sim.population.props.at[child_id, 'ckd_on_treatment'] = False + self.sim.population.props.at[child_id, 'ckd_date_treatment'] = pd.NaT + self.sim.population.props.at[child_id, 'ckd_stage_at_which_treatment_given'] = 'none' + self.sim.population.props.at[child_id, 'ckd_diagnosed'] = False + self.sim.population.props.at[child_id, 'ckd_date_diagnosis'] = pd.NaT + + def on_simulation_end(self) -> None: + pass + + def make_the_linear_models(self) -> None: + """Make and save LinearModels that will be used when the module is running""" + pass + + def look_up_consumable_item_codes(self): + """Look up the item codes used in the HSI of this module""" + pass + + +class CMDChronicKidneyDiseasePollEvent(RegularEvent, PopulationScopeEventMixin): + """An event that controls the development process of Diabetes Retinopathy (DR) and logs current states. DR diagnosis + begins at least after 3 years of being infected with Diabetes Mellitus.""" + + def __init__(self, module): + super().__init__(module, frequency=DateOffset(months=1)) + + def apply(self, population: Population) -> None: + pass + + def do_at_generic_first_appt( + self, + person_id: int, + individual_properties: IndividualProperties, + schedule_hsi_event: HSIEventScheduler, + **kwargs, + ) -> None: + pass + + +class CMDChronicKidneyDiseaseLoggingEvent(RegularEvent, PopulationScopeEventMixin): + """The only logging event for this module""" + + def __init__(self, module): + """schedule logging to repeat every 1 month + """ + self.repeat = 30 + super().__init__(module, frequency=DateOffset(days=self.repeat)) + + def apply(self, population): + """Compute statistics regarding the current status of persons and output to the logger + """ + pass From 85ea8d52c3a4012bca73cad07d761719f8c224c5 Mon Sep 17 00:00:00 2001 From: thewati Date: Mon, 25 Aug 2025 15:30:09 +0200 Subject: [PATCH 02/31] linear models --- src/tlo/methods/cmd_chronic_kidney_disease.py | 147 ++++++++++++------ 1 file changed, 102 insertions(+), 45 deletions(-) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index ec3a91150a..c27ef005b6 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -1,5 +1,5 @@ from pathlib import Path -from typing import Union +# from typing import Union import numpy as np import pandas as pd @@ -7,10 +7,10 @@ from tlo import DateOffset, Module, Parameter, Population, Property, Simulation, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata, cardio_metabolic_disorders +from tlo.methods import Metadata from tlo.methods.hsi_event import HSI_Event from tlo.methods.hsi_generic_first_appts import HSIEventScheduler -from tlo.methods.symptommanager import Symptom +# from tlo.methods.symptommanager import Symptom from tlo.population import IndividualProperties logger = logging.getLogger(__name__) @@ -31,19 +31,25 @@ class CMDChronicKidneyDisease(Module): } PARAMETERS = { - "rate_onset_to_stage1-4": Parameter(Types.REAL, + "rate_onset_to_stage1_4": Parameter(Types.REAL, "Probability of people who get diagnosed with CKD stage 1-4"), - "rate_stage1-4_to_stage5": Parameter(Types.REAL, + "rate_stage1_4_to_stage5": Parameter(Types.REAL, "Probability of people who get diagnosed with stage 5 (ESRD)"), - "init_prob_any_ckd": Parameter(Types.LIST, "Initial probability of anyone with diabetic retinopathy"), - "rp_ckd_ex_alc": Parameter( - Types.REAL, "relative prevalence at baseline of CKD if excessive alcohol" + "init_prob_any_ckd": Parameter(Types.LIST, "Initial probability of anyone with CKD"), + "rp_ckd_nc_diabetes": Parameter( + Types.REAL, "relative prevalence at baseline of CKD if person has diabetes" ), - "rp_ckd_high_sugar": Parameter( - Types.REAL, "relative prevalence at baseline of CKD if high sugar" + "rp_ckd_hiv_infection": Parameter( + Types.REAL, "relative prevalence at baseline of CKD if person has an HIV infection" ), - "rp_ckd_low_ex": Parameter( - Types.REAL, "relative prevalence at baseline of CKD if low exercise" + "rp_ckd_li_bmi": Parameter( + Types.REAL, "relative prevalence at baseline of CKD if person li_bmi is true" + ), + "rp_ckd_nc_hypertension": Parameter( + Types.REAL, "relative prevalence at baseline of CKD if person has hypertension" + ), + "rp_ckd_nc_chronic_ischemic_hd": Parameter( + Types.REAL, "relative prevalence at baseline of CKD if person has heart disease" ), } @@ -51,7 +57,7 @@ class CMDChronicKidneyDisease(Module): "ckd_status": Property( Types.CATEGORICAL, "CKD status", - categories=["none", "stage1-4", "stage5"], + categories=["pre_diagnosis", "stage1_4", "stage5"], ), "ckd_on_treatment": Property( Types.BOOL, "Whether this person is on CKD treatment", @@ -63,7 +69,7 @@ class CMDChronicKidneyDisease(Module): "ckd_stage_at_which_treatment_given": Property( Types.CATEGORICAL, "The CKD stage at which treatment was given (used to apply stage-specific treatment effect)", - categories=["stage1-4", "stage5", "chronic_dialysis"] + categories=["pre_diagnosis", "stage1_4", "stage5"] ), "ckd_diagnosed": Property( Types.BOOL, "Whether this person has been diagnosed with any CKD" @@ -88,14 +94,16 @@ def read_parameters(self, data_folder: str | Path) -> None: """ # TODO Read from resourcefile - self.parameters['rate_onset_to_stage1-4'] = 0.29 - self.parameters['rate_stage1-4_to_stage5'] = 0.4 + self.parameters['rate_onset_to_stage1_4'] = 0.29 + self.parameters['rate_stage1_4_to_stage5'] = 0.4 self.parameters['init_prob_any_ckd'] = [0.5, 0.3, 0.2] - self.parameters['rp_ckd_ex_alc'] = 1.1 - self.parameters['rp_ckd_high_sugar'] = 1.2 - self.parameters['rp_ckd_low_ex'] = 1.3 + self.parameters['rp_ckd_nc_diabetes'] = 1.1 + self.parameters['rp_ckd_hiv_infection'] = 1.2 + self.parameters['rp_ckd_li_bmi'] = 1.3 + self.parameters['rp_ckd_nc_hypertension'] = 1.3 + self.parameters['rp_ckd_nc_chronic_ischemic_hd'] = 1.2 #todo use chronic_kidney_disease_symptoms from cardio_metabolic_disorders.py @@ -115,24 +123,13 @@ def initialise_population(self, population: Population) -> None: alive_ckd_idx = df.loc[df.is_alive & df.nc_chronic_kidney_disease].index - # any_dr_idx = alive_diabetes_idx[ - # self.rng.random_sample(size=len(alive_diabetes_idx)) < self.parameters['init_prob_any_dr'] - # ] - # no_dr_idx = set(alive_diabetes_idx) - set(any_dr_idx) - # - # proliferative_dr_idx = any_dr_idx[ - # self.rng.random_sample(size=len(any_dr_idx)) < self.parameters['init_prob_proliferative_dr'] - # ] - # - # mild_dr_idx = set(any_dr_idx) - set(proliferative_dr_idx) - # write to property: - df.loc[df.is_alive & ~df.nc_chronic_kidney_disease, 'ckd_status'] = 'none' + df.loc[df.is_alive & ~df.nc_chronic_kidney_disease, 'ckd_status'] = 'pre_diagnosis' df.loc[list(alive_ckd_idx), "ckd_on_treatment"] = False df.loc[list(alive_ckd_idx), "ckd_diagnosed"] = False df.loc[list(alive_ckd_idx), "ckd_date_treatment"] = pd.NaT - df.loc[list(alive_ckd_idx), "ckd_stage_at_which_treatment_given"] = "none" + df.loc[list(alive_ckd_idx), "ckd_stage_at_which_treatment_given"] = "pre_diagnosis" df.loc[list(alive_ckd_idx), "ckd_date_diagnosis"] = pd.NaT # -------------------- ckd_status ----------- @@ -143,20 +140,23 @@ def initialise_population(self, population: Population) -> None: lm_init_ckd_status_any_ckd = LinearModel( LinearModelType.MULTIPLICATIVE, sum(self.parameters['init_prob_any_ckd']), - Predictor('li_ex_alc').when(True, self.parameters['rp_ckd_ex_alc']), - Predictor('li_high_sugar').when(True, self.parameters['rp_ckd_high_sugar']), - Predictor('li_low_ex').when(True, self.parameters['rp_ckd_low_ex']), + Predictor('nc_diabetes').when(True, self.parameters['rp_ckd_nc_diabetes']), + Predictor('hv_inf').when(True, self.parameters['rp_ckd_hiv_infection']), + Predictor('li_bmi').when(True, self.parameters['rp_ckd_li_bmi']), + Predictor('nc_hypertension').when(True, self.parameters['rp_ckd_nc_hypertension']), + Predictor('nc_chronic_ischemic_hd').when(True, self.parameters['rp_ckd_nc_chronic_ischemic_hd']), + ) # Get boolean Series of who has ckd has_ckd = lm_init_ckd_status_any_ckd.predict(df.loc[df.is_alive & df.nc_chronic_kidney_disease], self.rng) - # Get indices of those with DR + # Get indices of those with CKD ckd_idx = has_ckd[has_ckd].index if has_ckd.any() else pd.Index([]) if not ckd_idx.empty: - # Get non-none categories - categories = [cat for cat in df.ckd_status.cat.categories if cat != 'none'] + # Get non-pre_diagnosis categories + categories = [cat for cat in df.ckd_status.cat.categories if cat != 'pre_diagnosis'] # Verify probabilities match categories assert len(categories) == len(self.parameters['init_prob_any_ckd']) @@ -165,7 +165,7 @@ def initialise_population(self, population: Population) -> None: total_prob = sum(self.parameters['init_prob_any_ckd']) probs = [p / total_prob for p in self.parameters['init_prob_any_ckd']] - # Assign DR stages + # Assign CKD stages df.loc[ckd_idx, 'ckd_status'] = self.rng.choice( categories, size=len(ckd_idx), @@ -190,10 +190,10 @@ def on_birth(self, mother_id: int, child_id: int) -> None: """ Set properties of a child when they are born. :param child_id: the new child """ - self.sim.population.props.at[child_id, 'ckd_status'] = 'none' + self.sim.population.props.at[child_id, 'ckd_status'] = 'pre_diagnosis' self.sim.population.props.at[child_id, 'ckd_on_treatment'] = False self.sim.population.props.at[child_id, 'ckd_date_treatment'] = pd.NaT - self.sim.population.props.at[child_id, 'ckd_stage_at_which_treatment_given'] = 'none' + self.sim.population.props.at[child_id, 'ckd_stage_at_which_treatment_given'] = 'pre_diagnosis' self.sim.population.props.at[child_id, 'ckd_diagnosed'] = False self.sim.population.props.at[child_id, 'ckd_date_diagnosis'] = pd.NaT @@ -202,7 +202,20 @@ def on_simulation_end(self) -> None: def make_the_linear_models(self) -> None: """Make and save LinearModels that will be used when the module is running""" - pass + self.lm = dict() + + self.lm['onset_stage1_4'] = LinearModel( + LinearModelType.MULTIPLICATIVE, + intercept=self.parameters['rate_onset_to_stage1_4'] + ) + + self.lm['stage1_to_4_stage5'] = LinearModel( + LinearModelType.MULTIPLICATIVE, + self.parameters['rate_stage1_4_to_stage5'], + Predictor('had_treatment_during_this_stage', external=True) + .when(True, 0.0).otherwise(1.0) + ) + def look_up_consumable_item_codes(self): """Look up the item codes used in the HSI of this module""" @@ -210,14 +223,33 @@ def look_up_consumable_item_codes(self): class CMDChronicKidneyDiseasePollEvent(RegularEvent, PopulationScopeEventMixin): - """An event that controls the development process of Diabetes Retinopathy (DR) and logs current states. DR diagnosis - begins at least after 3 years of being infected with Diabetes Mellitus.""" + """An event that controls the development process of Chronic Kidney Disease (CKD) and logs current states.""" def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) def apply(self, population: Population) -> None: - pass + df = population.props + + had_treatment_during_this_stage = \ + df.is_alive & ~pd.isnull(df.ckd_date_treatment) & \ + (df.ckd_status == df.ckd_stage_at_which_treatment_given) + + ckd_and_alive_prediagnosis = ( + df.loc)[df.is_alive & df.nc_chronic_kidney_disease & (df.ckd_status == 'pre_diagnosis')] + ckd_and_alive_stage1_to_4 = df.loc[df.is_alive & df.nc_chronic_kidney_disease & (df.ckd_status == 'stage1_4')] + + will_progress = self.module.lm['onset_stage1_4'].predict(ckd_and_alive_prediagnosis, self.module.rng) + will_progress_idx = df.index[np.where(will_progress)[0]] + df.loc[will_progress_idx, 'ckd_status'] = 'stage1_4' + + stage1_to_4_to_stage5 = self.module.lm['stage1_to_4_stage5'].predict( + ckd_and_alive_stage1_to_4, + self.module.rng, + had_treatment_during_this_stage=had_treatment_during_this_stage) + stage1_to_4_to_stage5_idx = df.index[np.where(stage1_to_4_to_stage5)[0]] + df.loc[stage1_to_4_to_stage5_idx, 'ckd_status'] = 'stage5' + def do_at_generic_first_appt( self, @@ -229,6 +261,31 @@ def do_at_generic_first_appt( pass +class HSI_Renal_Clinic_and_Medication(HSI_Event, IndividualScopeEventMixin): + """ + This is the event when a person undergoes the optical coherence topography before being given the anti-vegf + injection. Given to individuals with dr_status of severe and proliferative + """ + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + assert isinstance(module, CMDChronicKidneyDisease) + + # Define the necessary information for an HSI + self.TREATMENT_ID = 'Dr_CMD_Renal_Medication' + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) + self.ACCEPTED_FACILITY_LEVEL = '3' #todo Facility Level? + self.ALERT_OTHER_DISEASES = [] + + def apply(self, person_id, squeeze_factor): + logger.debug(key='debug', + data=f'This is HSI_Renal_Clinic_and_Medication for person {person_id}') + df = self.sim.population.props + # person = df.loc[person_id] + # hs = self.sim.modules["HealthSystem"] + pass + + class CMDChronicKidneyDiseaseLoggingEvent(RegularEvent, PopulationScopeEventMixin): """The only logging event for this module""" From 28d0d3729116efdca607ab682ca3f4847961f7bb Mon Sep 17 00:00:00 2001 From: thewati Date: Mon, 15 Sep 2025 10:20:20 +0200 Subject: [PATCH 03/31] Transplant Evaluation HSI --- resources/healthsystem/consumables/README.md | 68 +------------------ src/tlo/methods/cmd_chronic_kidney_disease.py | 24 +++++++ 2 files changed, 27 insertions(+), 65 deletions(-) diff --git a/resources/healthsystem/consumables/README.md b/resources/healthsystem/consumables/README.md index fde597e51f..c6b25451de 100644 --- a/resources/healthsystem/consumables/README.md +++ b/resources/healthsystem/consumables/README.md @@ -1,65 +1,3 @@ -## Consumable Availability Scenarios - -This README describes the different **scenarios** used in consumable availability analysis. Each scenario corresponds to a specific level of availability of consumables across health facilities and months. The data for these scenarios are stored in columns such as `available_prop_scenario1`, `available_prop_scenario2`, etc., in the `ResourceFile_Consumables_availability_small.csv`. This is generated by the the following scripts - 1. `consumable_availability_estimation.py` generates the `available_prop` column representing the proportion of instances that a consumables is likely to be available given the levels of availability observed during 2018, 2. `generate_consumable_availability_scenarios_for_impact_analysis.py` generates additional scenarios of consumable availability ans appends them to the file generated by the first script. - -The scenario names below can be specified as parameters for `cons_availability` under `HealthSystem` when running simulations. - ---- - -### **Base Scenario** -- `default` - - **Description:** Actual 2018 availability estimates based on OpenLMIS data. - - **Purpose:** Serves as the baseline for comparisons. - ---- - -### **Scenarios 1–5: System-Level Improvements at Level 1a + 1b ** -These are based on regression analysis performed in [Mohan et al (2024)](https://pubmed.ncbi.nlm.nih.gov/38762283/). - -| Scenario | Column Name | Description | -|----------|---------------------------|-------------| -| 1 | `scenario1` | All items perform like *non-drug/diagnostic* consumables. | -| 2 | `scenario2` | Scenario 1 + all items match performance of *vital* medicines. | -| 3 | `scenario3` | Scenario 2 + all facilities match those managed by *pharmacists*. | -| 4 | `scenario4` | Scenario 3 + Level 1a facilities perform like Level 1b. | -| 5 | `scenario5` | Scenario 4 + All facilities perform like *CHAM* facilities. | - ---- - -### **Scenarios 6–9: Benchmarking Against Best Facilities** -| Scenario | Column Name | Description | -|----------|---------------------------|-------------| -| 6 | `scenario6` | All Level 1a/1b facilities match the *75th percentile* facility for each item. | -| 7 | `scenario7` | Same as above but using the *90th percentile*. | -| 8 | `scenario8` | Same as above but using the *99th percentile*. | -| 9 | `scenario9` | Level 1a, 1b, and 2 facilities match the *99th percentile*. | - ---- - -### **Scenarios 10–11: Horizontal Supply Chain Alignment** -| Scenario | Column Name | Description | -|----------|---------------------------|-------------| -| 10 | `scenario10` | All programs perform as well as *HIV* programs (only levels 1a and 1b updated). | -| 11 | `scenario11` | All programs perform as well as *EPI* programs (only levels 1a and 1b updated). | - -Scenarios 6-8 and 10-11 only include levels 1a and 1b where most of the service is delivered and consumable availability is particularly a challenge to match up with the regression-analysis-based scenarios 1-5 - ---- - -### **Scenarios 12–15: HIV Drug Supply Adjustments** -| Scenario | Column Name | Description | -|----------|---------------------------|-------------| -| 12 | `scenario12` | HIV performs as well (i.e. as badly) as general programs (excluding EPI and Cancer), benchmarked at *Facility_Level*. | -| 13 | `scenario13` | HIV performs as well as other programs, benchmarked at *Facility_ID*. | -| 14 | `scenario14` | Same as Scenario 13, but availability scaled *up* by 25%. | -| 15 | `scenario15` | Same as Scenario 13, but availability scaled *down* by 25%. | - ---- - -### **Upper Bound Scenario** -- **`all`** - - **Description:** All consumables are always available (i.e., 100% availability). - - **Purpose:** Represents the theoretical upper bound for health gains due to supply improvements. - ---- - +version https://git-lfs.github.com/spec/v1 +oid sha256:8caacbfcbf94df4a24a2bba66a71bf8f89514527dd32409a2e9d3bc69e177bfe +size 3913 diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index c27ef005b6..c0633c4b7d 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -286,6 +286,30 @@ def apply(self, person_id, squeeze_factor): pass +class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): + """ + This is the event a person undergoes in order to determine whether an individual is eligible for a kidney transplant + """ + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + assert isinstance(module, CMDChronicKidneyDisease) + + # Define the necessary information for an HSI + self.TREATMENT_ID = 'Dr_CMD_Kidney_Transplant_Evaluation' + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) + self.ACCEPTED_FACILITY_LEVEL = '3' + self.ALERT_OTHER_DISEASES = [] + + def apply(self, person_id, squeeze_factor): + logger.debug(key='debug', + data=f'This is HSI_Kidney_Transplant_Evaluation for person {person_id}') + df = self.sim.population.props + # person = df.loc[person_id] + # hs = self.sim.modules["HealthSystem"] + pass + + class CMDChronicKidneyDiseaseLoggingEvent(RegularEvent, PopulationScopeEventMixin): """The only logging event for this module""" From cd50f1504f3433203821046e6cb261fadd013ca0 Mon Sep 17 00:00:00 2001 From: thewati Date: Mon, 22 Sep 2025 09:59:38 +0200 Subject: [PATCH 04/31] restrict init_prob_any_ckd to 2, not 3 --- src/tlo/methods/cmd_chronic_kidney_disease.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index c0633c4b7d..b4b9b630b2 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -97,7 +97,7 @@ def read_parameters(self, data_folder: str | Path) -> None: self.parameters['rate_onset_to_stage1_4'] = 0.29 self.parameters['rate_stage1_4_to_stage5'] = 0.4 - self.parameters['init_prob_any_ckd'] = [0.5, 0.3, 0.2] + self.parameters['init_prob_any_ckd'] = [0.6, 0.4] self.parameters['rp_ckd_nc_diabetes'] = 1.1 self.parameters['rp_ckd_hiv_infection'] = 1.2 From 2a6d4ece012b4f73956c094a12a2ac08c24bf2ec Mon Sep 17 00:00:00 2001 From: thewati Date: Mon, 22 Sep 2025 10:41:16 +0200 Subject: [PATCH 05/31] eligible population in polling --- src/tlo/methods/cmd_chronic_kidney_disease.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index b4b9b630b2..f52c39dd0f 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -250,6 +250,15 @@ def apply(self, population: Population) -> None: stage1_to_4_to_stage5_idx = df.index[np.where(stage1_to_4_to_stage5)[0]] df.loc[stage1_to_4_to_stage5_idx, 'ckd_status'] = 'stage5' + # ----------------------------SELECTING INDIVIDUALS FOR CKD Diagnosis by stage----------------------------# + + eligible_population_ckd_screening = ( + (df.is_alive & df.nc_chronic_kidney_disease) & + (df.ckd_status == 'pre_diagnosis') & + (df.age_years >= 20) & + (pd.isna(df.ckd_date_diagnosis)) + ) + def do_at_generic_first_appt( self, From 2368aff353055bcf588d9c30418f63b8d950e45c Mon Sep 17 00:00:00 2001 From: thewati Date: Tue, 23 Sep 2025 08:54:32 +0200 Subject: [PATCH 06/31] go to different HSIs from polling --- src/tlo/methods/cmd_chronic_kidney_disease.py | 118 ++++++++++++++++-- 1 file changed, 110 insertions(+), 8 deletions(-) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index f52c39dd0f..8251a33e20 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -51,6 +51,12 @@ class CMDChronicKidneyDisease(Module): "rp_ckd_nc_chronic_ischemic_hd": Parameter( Types.REAL, "relative prevalence at baseline of CKD if person has heart disease" ), + "prob_ckd_renal_clinic": Parameter( + Types.REAL, "Proportion of eligible population that goes for Renal Clinic & Medication" + ), + "prob_ckd_transplant_eval": Parameter( + Types.REAL, "Proportion of eligible population that goes for Kidney Transplant Evaluation" + ), } PROPERTIES = { @@ -105,6 +111,10 @@ def read_parameters(self, data_folder: str | Path) -> None: self.parameters['rp_ckd_nc_hypertension'] = 1.3 self.parameters['rp_ckd_nc_chronic_ischemic_hd'] = 1.2 + # + self.parameters['prob_ckd_renal_clinic'] = 0.7 + self.parameters['prob_ckd_transplant_eval'] = 0.3 + #todo use chronic_kidney_disease_symptoms from cardio_metabolic_disorders.py # self.sim.modules['SymptomManager'].register_symptom( @@ -219,7 +229,22 @@ def make_the_linear_models(self) -> None: def look_up_consumable_item_codes(self): """Look up the item codes used in the HSI of this module""" - pass + get_item_codes = self.sim.modules['HealthSystem'].get_item_code_from_item_name + + self.cons_item_codes = dict() + self.cons_item_codes['renal_consumables'] = { + get_item_codes("Sterile syringe"): 1, + get_item_codes('Sterile drapes and supplies'): 3, + get_item_codes('Gloves, exam, latex, disposable, pair'): 4, + get_item_codes("Catheter"): 1, + get_item_codes("Disinfectant"): 1 + }, + self.cons_item_codes['kidney_transplant_eval_cons'] = { + get_item_codes("Blood collection tube"): 1, + get_item_codes('Reagents'): 3, + get_item_codes('Radiopharmaceuticals'): 4 + } + class CMDChronicKidneyDiseasePollEvent(RegularEvent, PopulationScopeEventMixin): @@ -252,13 +277,42 @@ def apply(self, population: Population) -> None: # ----------------------------SELECTING INDIVIDUALS FOR CKD Diagnosis by stage----------------------------# - eligible_population_ckd_screening = ( + eligible_population = ( (df.is_alive & df.nc_chronic_kidney_disease) & (df.ckd_status == 'pre_diagnosis') & (df.age_years >= 20) & (pd.isna(df.ckd_date_diagnosis)) ) + eligible_idx = df.loc[eligible_population].index + + if not eligible_idx.empty: + probs = [ + self.module.parameters['prob_ckd_renal_clinic'], + self.module.parameters['prob_ckd_transplant_eval'] + ] + + hsi_choices = self.module.rng.choice( + ['renal_clinic', 'transplant_eval'], + size=len(eligible_idx), + p=probs + ) + + for person_id, hsi_choice in zip(eligible_idx, hsi_choices): + if hsi_choice == 'renal_clinic': + self.sim.modules['HealthSystem'].schedule_hsi_event( + hsi_event=HSI_Renal_Clinic_and_Medication(self.module, person_id), + priority=1, + topen=self.sim.date, + tclose=self.sim.date + DateOffset(months=1), + ) + elif hsi_choice == 'transplant_eval': + self.sim.modules['HealthSystem'].schedule_hsi_event( + hsi_event=HSI_Kidney_Transplant_Evaluation(self.module, person_id), + priority=1, + topen=self.sim.date, + tclose=self.sim.date + DateOffset(months=1), + ) def do_at_generic_first_appt( self, @@ -290,9 +344,34 @@ def apply(self, person_id, squeeze_factor): logger.debug(key='debug', data=f'This is HSI_Renal_Clinic_and_Medication for person {person_id}') df = self.sim.population.props - # person = df.loc[person_id] - # hs = self.sim.modules["HealthSystem"] - pass + person = df.loc[person_id] + hs = self.sim.modules["HealthSystem"] + + if not df.at[person_id, 'is_alive']: + # The person is not alive, the event did not happen: so return a blank footprint + return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + + # if person already on treatment or not yet diagnosed, do nothing + if person["ckd_on_treatment"] or not person["ckd_diagnosed"]: + return self.sim.modules["HealthSystem"].get_blank_appt_footprint() + + assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) + + is_cons_available = self.get_consumables( + self.module.cons_item_codes['renal_consumables'] + ) + + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='renal_clinic_test', + hsi_event=self + ) + + if dx_result and is_cons_available: + # record date of diagnosis + df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date + df.at[person_id, 'ckd_date_treatment'] = self.sim.date + df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] + class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): @@ -314,9 +393,32 @@ def apply(self, person_id, squeeze_factor): logger.debug(key='debug', data=f'This is HSI_Kidney_Transplant_Evaluation for person {person_id}') df = self.sim.population.props - # person = df.loc[person_id] - # hs = self.sim.modules["HealthSystem"] - pass + person = df.loc[person_id] + hs = self.sim.modules["HealthSystem"] + if not df.at[person_id, 'is_alive']: + # The person is not alive, the event did not happen: so return a blank footprint + return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + + # if person already on treatment or not yet diagnosed, do nothing + if person["ckd_on_treatment"] or not person["ckd_diagnosed"]: + return self.sim.modules["HealthSystem"].get_blank_appt_footprint() + + assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) + + is_cons_available = self.get_consumables( + self.module.cons_item_codes['kidney_transplant_eval_cons'] + ) + + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='kidney_transplant_eval_tests', + hsi_event=self + ) + + if dx_result and is_cons_available: + # record date of diagnosis + df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date + df.at[person_id, 'ckd_date_treatment'] = self.sim.date + df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] class CMDChronicKidneyDiseaseLoggingEvent(RegularEvent, PopulationScopeEventMixin): From 5fc04691581a7a23d80fe6179026f3cb436b1e45 Mon Sep 17 00:00:00 2001 From: Precious29-web Date: Mon, 6 Oct 2025 10:45:17 +0200 Subject: [PATCH 07/31] readme --- resources/healthsystem/consumables/README.md | 68 +------------------- 1 file changed, 3 insertions(+), 65 deletions(-) diff --git a/resources/healthsystem/consumables/README.md b/resources/healthsystem/consumables/README.md index fde597e51f..c6b25451de 100644 --- a/resources/healthsystem/consumables/README.md +++ b/resources/healthsystem/consumables/README.md @@ -1,65 +1,3 @@ -## Consumable Availability Scenarios - -This README describes the different **scenarios** used in consumable availability analysis. Each scenario corresponds to a specific level of availability of consumables across health facilities and months. The data for these scenarios are stored in columns such as `available_prop_scenario1`, `available_prop_scenario2`, etc., in the `ResourceFile_Consumables_availability_small.csv`. This is generated by the the following scripts - 1. `consumable_availability_estimation.py` generates the `available_prop` column representing the proportion of instances that a consumables is likely to be available given the levels of availability observed during 2018, 2. `generate_consumable_availability_scenarios_for_impact_analysis.py` generates additional scenarios of consumable availability ans appends them to the file generated by the first script. - -The scenario names below can be specified as parameters for `cons_availability` under `HealthSystem` when running simulations. - ---- - -### **Base Scenario** -- `default` - - **Description:** Actual 2018 availability estimates based on OpenLMIS data. - - **Purpose:** Serves as the baseline for comparisons. - ---- - -### **Scenarios 1–5: System-Level Improvements at Level 1a + 1b ** -These are based on regression analysis performed in [Mohan et al (2024)](https://pubmed.ncbi.nlm.nih.gov/38762283/). - -| Scenario | Column Name | Description | -|----------|---------------------------|-------------| -| 1 | `scenario1` | All items perform like *non-drug/diagnostic* consumables. | -| 2 | `scenario2` | Scenario 1 + all items match performance of *vital* medicines. | -| 3 | `scenario3` | Scenario 2 + all facilities match those managed by *pharmacists*. | -| 4 | `scenario4` | Scenario 3 + Level 1a facilities perform like Level 1b. | -| 5 | `scenario5` | Scenario 4 + All facilities perform like *CHAM* facilities. | - ---- - -### **Scenarios 6–9: Benchmarking Against Best Facilities** -| Scenario | Column Name | Description | -|----------|---------------------------|-------------| -| 6 | `scenario6` | All Level 1a/1b facilities match the *75th percentile* facility for each item. | -| 7 | `scenario7` | Same as above but using the *90th percentile*. | -| 8 | `scenario8` | Same as above but using the *99th percentile*. | -| 9 | `scenario9` | Level 1a, 1b, and 2 facilities match the *99th percentile*. | - ---- - -### **Scenarios 10–11: Horizontal Supply Chain Alignment** -| Scenario | Column Name | Description | -|----------|---------------------------|-------------| -| 10 | `scenario10` | All programs perform as well as *HIV* programs (only levels 1a and 1b updated). | -| 11 | `scenario11` | All programs perform as well as *EPI* programs (only levels 1a and 1b updated). | - -Scenarios 6-8 and 10-11 only include levels 1a and 1b where most of the service is delivered and consumable availability is particularly a challenge to match up with the regression-analysis-based scenarios 1-5 - ---- - -### **Scenarios 12–15: HIV Drug Supply Adjustments** -| Scenario | Column Name | Description | -|----------|---------------------------|-------------| -| 12 | `scenario12` | HIV performs as well (i.e. as badly) as general programs (excluding EPI and Cancer), benchmarked at *Facility_Level*. | -| 13 | `scenario13` | HIV performs as well as other programs, benchmarked at *Facility_ID*. | -| 14 | `scenario14` | Same as Scenario 13, but availability scaled *up* by 25%. | -| 15 | `scenario15` | Same as Scenario 13, but availability scaled *down* by 25%. | - ---- - -### **Upper Bound Scenario** -- **`all`** - - **Description:** All consumables are always available (i.e., 100% availability). - - **Purpose:** Represents the theoretical upper bound for health gains due to supply improvements. - ---- - +version https://git-lfs.github.com/spec/v1 +oid sha256:8caacbfcbf94df4a24a2bba66a71bf8f89514527dd32409a2e9d3bc69e177bfe +size 3913 From 384977c31988b1cb552dc00bde184bc25831de95 Mon Sep 17 00:00:00 2001 From: Precious29-web Date: Mon, 3 Nov 2025 15:12:38 +0200 Subject: [PATCH 08/31] equipment added --- .../ResourceFile_EquipmentCatalogue.csv | 4 +- .../CVD.csv | 4 +- .../ClinicallyVulnerable.csv | 4 +- .../Default.csv | 4 +- .../EHP_III.csv | 4 +- .../LCOA_EHP.csv | 4 +- .../Naive.csv | 4 +- .../RMNCH.csv | 4 +- .../Test Mode 1.csv | 4 +- .../Test.csv | 4 +- .../VerticalProgrammes.csv | 4 +- src/tlo/methods/cmd_chronic_kidney_disease.py | 59 ++++++++++++++++++- 12 files changed, 78 insertions(+), 25 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv index 45f801f0c8..fce48e0e01 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ec5f619816df6150ae92839152607296a5f2289024c92ce6b5ba621d38db20b7 -size 33517 +oid sha256:1f5d9a062354c3bff9b830c200f8a4469fe11668198338443b0b4aa69aae2974 +size 34328 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv index ca43ac966e..208830975a 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2789315842d7b34bce2c4a34300d5b9eed104b1a4bbb93568c00ec5095adc6a -size 3946 +oid sha256:3562c4de5644ffed68f3c30fd29848aa8efd9f56626a4a3c779a5b1f5ba0c208 +size 4160 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv index 05cc5f6c12..568a9c4abe 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:69a56437c2d302aecdb2ca97459cf756c72d349b471dc66ad30e1a6f8d7d47c3 -size 3566 +oid sha256:bd663b2e4010df0ca920c0e078c2057ad6f0a3879e210d840b4de794f303483c +size 3780 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv index 0d457a30f0..751d643793 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2e5a056e2cfd1502b5a4e890c8d48120ae637aac7df147db18a91518b1747757 -size 3948 +oid sha256:d50a573e1d5d0573db2b1c23e24f0507240df2b3626a21f965f5c917ea674585 +size 4162 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv index 45972b4124..b090a56d80 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02bb2169513783163aad51bfddd02cc2196178f3f7b1a2c7c0e0a4eae6f51794 -size 3947 +oid sha256:3a4c155cd2cae6dad693f628e209c65033fb7ec112d5915b1d91e63e4eee0c36 +size 4161 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv index 176bae4f7c..d5db491247 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:46f8983d320984a51aa078117b90a6a78ed3c604e9ebc96e2c0e10a75659e4c3 -size 3946 +oid sha256:652faa2632b9bf1b06c2dfb9396dcc96fff86a75b6dee529b5d37d71af4f8185 +size 4160 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv index 12ff672d74..ff95348cde 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c3b3c34313cd97b0bc807e17e64e4997f25f2357c34a0f9ec1f6c834de7433d1 -size 3946 +oid sha256:17191d114a7ce860a08643cb90393808d4a862276d60db9631406c86d2950dd4 +size 4160 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv index 17e869fb79..93e8d6e4a0 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:550ad48b191933d53c75d006e13140585d2563487c7a4d115a7320b506bb363c -size 3930 +oid sha256:d560145b82ed13d6890b12b5019b689102931cf3671d39aca321b3c922506d5f +size 4144 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv index d307f514b9..2ad7b34b68 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bb18d408b2470f2a935a2f125e609d862bdd0d07455d84d0e6167d3f229daac -size 3948 +oid sha256:0174294294617bda9ca84e4f6876025d630b90ffa664624ec9977645c064dcc3 +size 4162 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv index a4bdd742a3..60070012a0 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bce2dcdade0e065b0f48f25edd7e32db45958309dedcf25872a55ee4d85a5bee -size 3948 +oid sha256:73ba874134fa02c5ac8a9b6f969567cc382c15599d31ef90db93e17a5b3510ef +size 4162 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv index 9a8ccef424..8741886071 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0e0d8a699cc8a4a04c2b2b8dd4f7368a1572d14906ae8d8654d81456c69e6055 -size 3946 +oid sha256:6b39870773046fa81f8e07d5462b87ec6d378e02f3ccde6e39564f4278e33327 +size 4160 diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 8251a33e20..e398dc89f2 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -335,9 +335,9 @@ def __init__(self, module, person_id): assert isinstance(module, CMDChronicKidneyDisease) # Define the necessary information for an HSI - self.TREATMENT_ID = 'Dr_CMD_Renal_Medication' + self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Renal_Medication' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' #todo Facility Level? + self.ACCEPTED_FACILITY_LEVEL = '3' self.ALERT_OTHER_DISEASES = [] def apply(self, person_id, squeeze_factor): @@ -367,6 +367,8 @@ def apply(self, person_id, squeeze_factor): ) if dx_result and is_cons_available: + self.add_equipment({'weighing scale','blood pressure machine', 'purple blood bottle', 'red blood bottle' + 'ultrasound machine', 'electrocardiogram', 'oxygen concentrator'}) # record date of diagnosis df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date df.at[person_id, 'ckd_date_treatment'] = self.sim.date @@ -384,7 +386,8 @@ def __init__(self, module, person_id): assert isinstance(module, CMDChronicKidneyDisease) # Define the necessary information for an HSI - self.TREATMENT_ID = 'Dr_CMD_Kidney_Transplant_Evaluation' + #todo need to update priority number in resource files + self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Kidney_Transplant_' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.ALERT_OTHER_DISEASES = [] @@ -420,6 +423,56 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ckd_date_treatment'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] +class HSI_Kidney_Transplant_Surgery(HSI_Event, IndividualScopeEventMixin): + """ + This is the event a person undergoes in order to determine whether an individual is eligible for a kidney transplant + """ + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + assert isinstance(module, CMDChronicKidneyDisease) + + # Define the necessary information for an HSI + #todo need to update priority number in resource files + self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Kidney_Transplant_' + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) + self.ACCEPTED_FACILITY_LEVEL = '3' + self.ALERT_OTHER_DISEASES = [] + + def apply(self, person_id, squeeze_factor): + logger.debug(key='debug', + data=f'This is HSI_Kidney_Transplant_Evaluation for person {person_id}') + df = self.sim.population.props + person = df.loc[person_id] + hs = self.sim.modules["HealthSystem"] + if not df.at[person_id, 'is_alive']: + # The person is not alive, the event did not happen: so return a blank footprint + return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + + # if person already on treatment or not yet diagnosed, do nothing + if person["ckd_on_treatment"] or not person["ckd_diagnosed"]: + return self.sim.modules["HealthSystem"].get_blank_appt_footprint() + + assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) + + is_cons_available = self.get_consumables( + self.module.cons_item_codes['kidney_transplant_eval_cons'] + ) + + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='kidney_transplant_eval_tests', + hsi_event=self + ) + + if dx_result and is_cons_available: + self.add_equipment({'Patient monitors', 'Infusion pump','Dialysis machine', 'Bloodlines','Water tank', + 'Reverse osmosis machine', 'Water softener', 'Carbon filter', '5 micro filter', + 'ventilator', 'Electrocautery unit', 'Suction machine', 'theatre bed', + 'cold static storage' 'perfusion machine', 'Ultrasound machine', 'drip stand', 'trolley',}) + # record date of diagnosis + df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date + df.at[person_id, 'ckd_date_treatment'] = self.sim.date + df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] class CMDChronicKidneyDiseaseLoggingEvent(RegularEvent, PopulationScopeEventMixin): """The only logging event for this module""" From 61f8b44bc32aa69b8f76b9f9571181a5caa80704 Mon Sep 17 00:00:00 2001 From: thewati Date: Tue, 18 Nov 2025 09:15:12 +0200 Subject: [PATCH 09/31] renal clinic next session and herbal medication --- src/tlo/methods/cmd_chronic_kidney_disease.py | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 8251a33e20..674b484a59 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -57,6 +57,12 @@ class CMDChronicKidneyDisease(Module): "prob_ckd_transplant_eval": Parameter( Types.REAL, "Proportion of eligible population that goes for Kidney Transplant Evaluation" ), + "prop_herbal_use_ckd": Parameter( + Types.REAL, "Proportion of people with ckd who use herbal medicine" + ), + "rp_ckd_herbal_use_baseline": Parameter( + Types.REAL, "Relative risk of having CKD at baseline if a person uses herbal medicine" + ), } PROPERTIES = { @@ -86,6 +92,9 @@ class CMDChronicKidneyDisease(Module): ), "ckd_total_dialysis_sessions": Property(Types.INT, "total number of dialysis sessions the person has ever had"), + "uses_herbal_medicine": Property( + Types.BOOL, "Whether this person uses herbal medicine" + ), } def __init__(self): @@ -110,11 +119,16 @@ def read_parameters(self, data_folder: str | Path) -> None: self.parameters['rp_ckd_li_bmi'] = 1.3 self.parameters['rp_ckd_nc_hypertension'] = 1.3 self.parameters['rp_ckd_nc_chronic_ischemic_hd'] = 1.2 + self.parameters['rp_ckd_herbal_use_baseline'] = 1.35 # self.parameters['prob_ckd_renal_clinic'] = 0.7 self.parameters['prob_ckd_transplant_eval'] = 0.3 + self.parameters['prop_herbal_use_ckd'] = 0.35 + + + #todo use chronic_kidney_disease_symptoms from cardio_metabolic_disorders.py # self.sim.modules['SymptomManager'].register_symptom( @@ -142,6 +156,11 @@ def initialise_population(self, population: Population) -> None: df.loc[list(alive_ckd_idx), "ckd_stage_at_which_treatment_given"] = "pre_diagnosis" df.loc[list(alive_ckd_idx), "ckd_date_diagnosis"] = pd.NaT + df.loc[list(alive_ckd_idx), "uses_herbal_medicine"] = \ + self.rng.random(len(alive_ckd_idx)) < self.parameters['prop_herbal_use_ckd'] + df.loc[list(df.loc[df.is_alive & ~df.nc_chronic_kidney_disease].index), + "uses_herbal_medicine"] = False + # -------------------- ckd_status ----------- # Determine who has CKD at all stages: # check parameters are sensible: probability of having any CKD stage cannot exceed 1.0 @@ -155,6 +174,7 @@ def initialise_population(self, population: Population) -> None: Predictor('li_bmi').when(True, self.parameters['rp_ckd_li_bmi']), Predictor('nc_hypertension').when(True, self.parameters['rp_ckd_nc_hypertension']), Predictor('nc_chronic_ischemic_hd').when(True, self.parameters['rp_ckd_nc_chronic_ischemic_hd']), + Predictor('uses_herbal_medicine').when(True, self.parameters['rp_ckd_herbal_use_baseline']), ) @@ -216,7 +236,9 @@ def make_the_linear_models(self) -> None: self.lm['onset_stage1_4'] = LinearModel( LinearModelType.MULTIPLICATIVE, - intercept=self.parameters['rate_onset_to_stage1_4'] + self.parameters['rate_onset_to_stage1_4'], + Predictor('uses_herbal_medicine') + .when(True, self.parameters['rp_ckd_herbal_use_baseline']) ) self.lm['stage1_to_4_stage5'] = LinearModel( @@ -226,7 +248,6 @@ def make_the_linear_models(self) -> None: .when(True, 0.0).otherwise(1.0) ) - def look_up_consumable_item_codes(self): """Look up the item codes used in the HSI of this module""" get_item_codes = self.sim.modules['HealthSystem'].get_item_code_from_item_name @@ -337,7 +358,7 @@ def __init__(self, module, person_id): # Define the necessary information for an HSI self.TREATMENT_ID = 'Dr_CMD_Renal_Medication' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' #todo Facility Level? + self.ACCEPTED_FACILITY_LEVEL = '3' #todo Facility Level? self.ALERT_OTHER_DISEASES = [] def apply(self, person_id, squeeze_factor): @@ -372,6 +393,11 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ckd_date_treatment'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] + next_session = self.sim.date + pd.DateOffset(months=1) + self.sim.modules['HealthSystem'].schedule_hsi_event(self, + topen=next_session, + tclose=None, + priority=1) class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): @@ -419,7 +445,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date df.at[person_id, 'ckd_date_treatment'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] - + class CMDChronicKidneyDiseaseLoggingEvent(RegularEvent, PopulationScopeEventMixin): """The only logging event for this module""" From 68a6ef10fec65595e4e52d56d692fe502d3cee1a Mon Sep 17 00:00:00 2001 From: Precious29-web Date: Mon, 10 Nov 2025 14:47:28 +0200 Subject: [PATCH 10/31] consumables --- src/tlo/methods/cmd_chronic_kidney_disease.py | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index e398dc89f2..01e65f780f 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -244,6 +244,33 @@ def look_up_consumable_item_codes(self): get_item_codes('Reagents'): 3, get_item_codes('Radiopharmaceuticals'): 4 } + self.cons_item_codes['Kidney_Transplant_Surgery'] = { + # Prepare surgical instruments + # administer an IV + get_item_codes('Cannula iv (winged with injection pot) 18_each_CMST'): 1, + get_item_codes("Giving set iv administration + needle 15 drops/ml_each_CMST"): 1, + get_item_codes("ringer's lactate (Hartmann's solution), 1000 ml_12_IDA"): 2000, + # request a general anaesthetic + get_item_codes("Halothane (fluothane)_250ml_CMST"): 100, + # Position patient in lateral position with kidney break + # clean the site of the surgery + get_item_codes("Chlorhexidine 1.5% solution_5_CMST"): 600, + # tools to begin surgery + get_item_codes("Scalpel blade size 22 (individually wrapped)_100_CMST"): 1, + # repair incision made + get_item_codes("Suture pack"): 1, + get_item_codes("Gauze, absorbent 90cm x 40m_each_CMST"): 100, + # administer pain killer + get_item_codes('Pethidine, 50 mg/ml, 2 ml ampoule'): 6, + # administer antibiotic + get_item_codes("Ampicillin injection 500mg, PFR_each_CMST"): 2, + #change this to immunosuppressive drugs + # equipment used by surgeon, gloves and facemask + get_item_codes('Disposables gloves, powder free, 100 pieces per box'): 1, + get_item_codes('surgical face mask, disp., with metal nose piece_50_IDA'): 1, + # request syringe + get_item_codes("Syringe, Autodisable SoloShot IX "): 1 + } @@ -474,6 +501,38 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ckd_date_treatment'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] + class HSI_CardioMetabolicDisorders_Dialysis_Refill(HSI_Event, IndividualScopeEventMixin): + """This is a Health System Interaction Event in which a person receives a dialysis session 2 times a week + adding up to 8 times a month.""" + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + + self.TREATMENT_ID = 'CardioMetabolicDisorders_Treatment_Haemodialysis' + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) + self.ACCEPTED_FACILITY_LEVEL = '3' + + def apply(self, person_id, squeeze_factor): + + df = self.sim.population.props + + if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'nc_chronic_kidney_disease']: + return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + + # Increment total number of dialysis sessions the person has ever had in their lifetime + df.at[person_id, 'nc_ckd_total_dialysis_sessions'] += 1 + + + self.add_equipment({'Chair', 'Dialysis Machine', 'Dialyser (Artificial Kidney)', + 'Bloodlines', 'Dialysate solution', 'Dialysis water treatment system'}) + + next_session_date = self.sim.date + pd.DateOffset(days=3) + self.sim.modules['HealthSystem'].schedule_hsi_event(self, + topen=next_session_date, + tclose=next_session_date + pd.DateOffset(days=1), + priority=1 + ) + class CMDChronicKidneyDiseaseLoggingEvent(RegularEvent, PopulationScopeEventMixin): """The only logging event for this module""" From 9f87e9b10078680f3a0a42ea81a486c1e54961bd Mon Sep 17 00:00:00 2001 From: thewati Date: Mon, 24 Nov 2025 15:01:11 +0200 Subject: [PATCH 11/31] renal clinic next session and herbal medication --- .../ResourceFile_EquipmentCatalogue.csv | 6 ++-- .../CVD.csv | 2 -- .../ClinicallyVulnerable.csv | 2 -- .../Default.csv | 2 -- .../EHP_III.csv | 2 -- .../LCOA_EHP.csv | 2 -- .../Naive.csv | 2 -- .../RMNCH.csv | 2 -- .../Test Mode 1.csv | 2 -- .../Test.csv | 2 -- .../VerticalProgrammes.csv | 2 -- src/tlo/methods/cmd_chronic_kidney_disease.py | 30 ++++++++++++++++--- 12 files changed, 28 insertions(+), 28 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv index 533c85b2b9..d79bdc2682 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f5d9a062354c3bff9b830c200f8a4469fe11668198338443b0b4aa69aae2974 -size 34328 -oid sha256:eba56688a2bd31802340feffdacf337522c494fa0e7e5b6fbc7f3ee171a79a2c -size 38006 +oid sha256:9e76003a2d742902f067636f16720289c02f8ab1bf763cb66c746734608e6109 +size 217 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv index 70e993564b..c14cccf83d 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3562c4de5644ffed68f3c30fd29848aa8efd9f56626a4a3c779a5b1f5ba0c208 -size 4160 oid sha256:be19c64d84ed49ae79be28f6a8e7abb8e279f7cb441ee90d934753731c708e12 size 4138 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv index 4a3530a8ec..ce5cf028a8 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:bd663b2e4010df0ca920c0e078c2057ad6f0a3879e210d840b4de794f303483c -size 3780 oid sha256:824c5882bf4f320e58e7cff622fd5abf5c2b581fd3ed4f2df2d8a2f919509cfd size 3742 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv index 981bcdc9bf..7a0dbc9d95 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d50a573e1d5d0573db2b1c23e24f0507240df2b3626a21f965f5c917ea674585 -size 4162 oid sha256:6b87874bafc8f792822ef1962070c62aead74992e7856a7ca749a05b340eb20a size 4140 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv index 3c61ae9e03..4425679ae9 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3a4c155cd2cae6dad693f628e209c65033fb7ec112d5915b1d91e63e4eee0c36 -size 4161 oid sha256:f97a75bf186809ed5edc41f4542e61bf5ebefb131d7617bbccd74ef5b5a4f33b size 4139 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv index 8b13435b14..3403c49229 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:652faa2632b9bf1b06c2dfb9396dcc96fff86a75b6dee529b5d37d71af4f8185 -size 4160 oid sha256:50502e603cb50e6ea00f6df494b4f0cd39f216ce1dffff93ad9d136cd4592c50 size 4138 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv index 5f4cb34b98..1f4d00fba8 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17191d114a7ce860a08643cb90393808d4a862276d60db9631406c86d2950dd4 -size 4160 oid sha256:c914b33a927d488ff650e7a39a1ba8e7b965c8cda73348595e74c2bb2a5fcf89 size 4138 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv index b18b964244..5f753fabab 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d560145b82ed13d6890b12b5019b689102931cf3671d39aca321b3c922506d5f -size 4144 oid sha256:838b3ccc7b5882631ae455634aa8d034065a928a63f5ecd412176556738e20b9 size 4118 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv index 5ebca8992b..8e745a1cb0 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0174294294617bda9ca84e4f6876025d630b90ffa664624ec9977645c064dcc3 -size 4162 oid sha256:38a678d31cacc4cff04f54d9a916cd5ff2f5f76641ac7bad316087056b8df16c size 4140 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv index 054b913a0c..7e0fd99721 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:73ba874134fa02c5ac8a9b6f969567cc382c15599d31ef90db93e17a5b3510ef -size 4162 oid sha256:4645d100a2b19ca1b50888d2233f7752d360763eb53ec9e61e4355c411b5f18d size 4140 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv index 1f9ec0bb4e..39e26b7a1d 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv @@ -1,5 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b39870773046fa81f8e07d5462b87ec6d378e02f3ccde6e39564f4278e33327 -size 4160 oid sha256:191d303273c80fa7a1fe59488b5c63e718b9f92911ab45f70ada1c98db06e086 size 4138 diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 8251a33e20..7c0667e30e 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -57,6 +57,12 @@ class CMDChronicKidneyDisease(Module): "prob_ckd_transplant_eval": Parameter( Types.REAL, "Proportion of eligible population that goes for Kidney Transplant Evaluation" ), + "prop_herbal_use_ckd": Parameter( + Types.REAL, "Proportion of people with ckd who use herbal medicine" + ), + "rp_ckd_herbal_use_baseline": Parameter( + Types.REAL, "Relative risk of having CKD at baseline if a person uses herbal medicine" + ), } PROPERTIES = { @@ -86,6 +92,9 @@ class CMDChronicKidneyDisease(Module): ), "ckd_total_dialysis_sessions": Property(Types.INT, "total number of dialysis sessions the person has ever had"), + "uses_herbal_medicine": Property( + Types.BOOL, "Whether this person uses herbal medicine" + ), } def __init__(self): @@ -110,10 +119,12 @@ def read_parameters(self, data_folder: str | Path) -> None: self.parameters['rp_ckd_li_bmi'] = 1.3 self.parameters['rp_ckd_nc_hypertension'] = 1.3 self.parameters['rp_ckd_nc_chronic_ischemic_hd'] = 1.2 + self.parameters['rp_ckd_herbal_use_baseline'] = 1.35 # self.parameters['prob_ckd_renal_clinic'] = 0.7 self.parameters['prob_ckd_transplant_eval'] = 0.3 + self.parameters['prop_herbal_use_ckd'] = 0.35 #todo use chronic_kidney_disease_symptoms from cardio_metabolic_disorders.py @@ -142,6 +153,11 @@ def initialise_population(self, population: Population) -> None: df.loc[list(alive_ckd_idx), "ckd_stage_at_which_treatment_given"] = "pre_diagnosis" df.loc[list(alive_ckd_idx), "ckd_date_diagnosis"] = pd.NaT + df.loc[list(alive_ckd_idx), "uses_herbal_medicine"] = \ + self.rng.random(len(alive_ckd_idx)) < self.parameters['prop_herbal_use_ckd'] + df.loc[list(df.loc[df.is_alive & ~df.nc_chronic_kidney_disease].index), + "uses_herbal_medicine"] = False + # -------------------- ckd_status ----------- # Determine who has CKD at all stages: # check parameters are sensible: probability of having any CKD stage cannot exceed 1.0 @@ -155,6 +171,7 @@ def initialise_population(self, population: Population) -> None: Predictor('li_bmi').when(True, self.parameters['rp_ckd_li_bmi']), Predictor('nc_hypertension').when(True, self.parameters['rp_ckd_nc_hypertension']), Predictor('nc_chronic_ischemic_hd').when(True, self.parameters['rp_ckd_nc_chronic_ischemic_hd']), + Predictor('uses_herbal_medicine').when(True, self.parameters['rp_ckd_herbal_use_baseline']), ) @@ -216,7 +233,9 @@ def make_the_linear_models(self) -> None: self.lm['onset_stage1_4'] = LinearModel( LinearModelType.MULTIPLICATIVE, - intercept=self.parameters['rate_onset_to_stage1_4'] + self.parameters['rate_onset_to_stage1_4'], + Predictor('uses_herbal_medicine') + .when(True, self.parameters['rp_ckd_herbal_use_baseline']) ) self.lm['stage1_to_4_stage5'] = LinearModel( @@ -226,7 +245,6 @@ def make_the_linear_models(self) -> None: .when(True, 0.0).otherwise(1.0) ) - def look_up_consumable_item_codes(self): """Look up the item codes used in the HSI of this module""" get_item_codes = self.sim.modules['HealthSystem'].get_item_code_from_item_name @@ -246,7 +264,6 @@ def look_up_consumable_item_codes(self): } - class CMDChronicKidneyDiseasePollEvent(RegularEvent, PopulationScopeEventMixin): """An event that controls the development process of Chronic Kidney Disease (CKD) and logs current states.""" @@ -337,7 +354,7 @@ def __init__(self, module, person_id): # Define the necessary information for an HSI self.TREATMENT_ID = 'Dr_CMD_Renal_Medication' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' #todo Facility Level? + self.ACCEPTED_FACILITY_LEVEL = '3' #todo Facility Level? self.ALERT_OTHER_DISEASES = [] def apply(self, person_id, squeeze_factor): @@ -372,6 +389,11 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ckd_date_treatment'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] + next_session = self.sim.date + pd.DateOffset(months=1) + self.sim.modules['HealthSystem'].schedule_hsi_event(self, + topen=next_session, + tclose=None, + priority=1) class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): From 2570e63944b1aa3c057c693f91db5312fe1c5765 Mon Sep 17 00:00:00 2001 From: Precious29-web Date: Tue, 25 Nov 2025 03:51:33 +0200 Subject: [PATCH 12/31] consumables for kidney transplant surgery --- src/tlo/methods/cmd_chronic_kidney_disease.py | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 8251a33e20..9174d726cc 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -245,6 +245,33 @@ def look_up_consumable_item_codes(self): get_item_codes('Radiopharmaceuticals'): 4 } + self.cons_item_codes['Kidney_Transplant_Surgery'] = { + # Prepare surgical instruments + # administer an IV + get_item_codes('Cannula iv (winged with injection pot) 18_each_CMST'): 1, + get_item_codes("Giving set iv administration + needle 15 drops/ml_each_CMST"): 1, + get_item_codes("ringer's lactate (Hartmann's solution), 1000 ml_12_IDA"): 2000, + # request a general anaesthetic + get_item_codes("Halothane (fluothane)_250ml_CMST"): 100, + # Position patient in lateral position with kidney break + # clean the site of the surgery + get_item_codes("Chlorhexidine 1.5% solution_5_CMST"): 600, + # tools to begin surgery + get_item_codes("Scalpel blade size 22 (individually wrapped)_100_CMST"): 1, + # repair incision made + get_item_codes("Suture pack"): 1, + get_item_codes("Gauze, absorbent 90cm x 40m_each_CMST"): 100, + # administer pain killer + get_item_codes('Pethidine, 50 mg/ml, 2 ml ampoule'): 6, + # administer antibiotic + get_item_codes("Ampicillin injection 500mg, PFR_each_CMST"): 2, + #change this to immunosuppressive drugs + # equipment used by surgeon, gloves and facemask + get_item_codes('Disposables gloves, powder free, 100 pieces per box'): 1, + get_item_codes('surgical face mask, disp., with metal nose piece_50_IDA'): 1, + # request syringe + get_item_codes("Syringe, Autodisable SoloShot IX "): 1 + } class CMDChronicKidneyDiseasePollEvent(RegularEvent, PopulationScopeEventMixin): From 02e94d355621cbd92dff50567162a9dfa1ab493c Mon Sep 17 00:00:00 2001 From: Precious29-web Date: Tue, 25 Nov 2025 04:05:50 +0200 Subject: [PATCH 13/31] kidney transplant HSI --- src/tlo/methods/cmd_chronic_kidney_disease.py | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 5e2f526def..79cd20bda1 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -421,7 +421,56 @@ def apply(self, person_id, squeeze_factor): topen=next_session, tclose=None, priority=1) + # Define the necessary information for an HSI + #todo need to update priority number in resource files + self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Kidney_Transplant_' + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) + self.ACCEPTED_FACILITY_LEVEL = '3' + self.ALERT_OTHER_DISEASES = [] + + def apply(self, person_id, squeeze_factor): + logger.debug(key='debug', + data=f'This is HSI_Kidney_Transplant_Evaluation for person {person_id}') + df = self.sim.population.props + person = df.loc[person_id] + hs = self.sim.modules["HealthSystem"] + if not df.at[person_id, 'is_alive']: + # The person is not alive, the event did not happen: so return a blank footprint + return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + + # if person already on treatment or not yet diagnosed, do nothing + if person["ckd_on_treatment"] or not person["ckd_diagnosed"]: + return self.sim.modules["HealthSystem"].get_blank_appt_footprint() + assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) + + is_cons_available = self.get_consumables( + self.module.cons_item_codes['kidney_transplant_eval_cons'] + ) + + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='kidney_transplant_eval_tests', + hsi_event=self + ) + + if dx_result and is_cons_available: + # record date of diagnosis + df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date + df.at[person_id, 'ckd_date_treatment'] = self.sim.date + df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] + +class HSI_Kidney_Transplant_Surgery(HSI_Event, IndividualScopeEventMixin): + """ + This is the event a person undergoes in order to determine whether an individual is eligible for a kidney transplant + """ + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + assert isinstance(module, CMDChronicKidneyDisease) + + # Define the necessary information for an HSI + #todo need to update priority number in resource files + self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Kidney_Transplant_' class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): """ From 2709cb2ad003f10bc69f2d6713b736957372a272 Mon Sep 17 00:00:00 2001 From: Precious29-web Date: Tue, 25 Nov 2025 06:52:25 +0200 Subject: [PATCH 14/31] kidney transplant HSI with equipment --- src/tlo/methods/cmd_chronic_kidney_disease.py | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 79cd20bda1..e46a292b35 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -428,6 +428,8 @@ def apply(self, person_id, squeeze_factor): self.ACCEPTED_FACILITY_LEVEL = '3' self.ALERT_OTHER_DISEASES = [] + + def apply(self, person_id, squeeze_factor): logger.debug(key='debug', data=f'This is HSI_Kidney_Transplant_Evaluation for person {person_id}') @@ -471,6 +473,47 @@ def __init__(self, module, person_id): # Define the necessary information for an HSI #todo need to update priority number in resource files self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Kidney_Transplant_' + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) + self.ACCEPTED_FACILITY_LEVEL = '3' + self.ALERT_OTHER_DISEASES = [] + + def apply(self, person_id, squeeze_factor): + logger.debug(key='debug', + data=f'This is HSI_Kidney_Transplant_surgery for person {person_id}') + df = self.sim.population.props + person = df.loc[person_id] + hs = self.sim.modules["HealthSystem"] + if not df.at[person_id, 'is_alive']: + # The person is not alive, the event did not happen: so return a blank footprint + return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + + # if person already on treatment or not yet diagnosed, do nothing + if person["ckd_on_treatment"] or not person["ckd_diagnosed"]: + return self.sim.modules["HealthSystem"].get_blank_appt_footprint() + + assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) + + is_cons_available = self.get_consumables( + self.module.cons_item_codes['kidney_transplant_surgery_cons'] + ) + + if dx_result and is_cons_available: + self.add_equipment({'Patient monitors', 'Infusion pump', 'Dialysis machine', 'Bloodlines', 'Water tank', + 'Reverse osmosis machine', 'Water softener', 'Carbon filter', '5 micro filter', + 'ventilator', 'Electrocautery unit', 'Suction machine', 'theatre bed', + 'cold static storage' 'perfusion machine', 'Ultrasound machine', 'drip stand', + 'trolley', }) + + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='kidney_transplant_surgery_tests', + hsi_event=self + ) + + if dx_result and is_cons_available: + # record date of diagnosis + df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date + df.at[person_id, 'ckd_date_treatment'] = self.sim.date + df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): """ From 5210b3692d1f141c2b240fbb71c69fe3b595cdb0 Mon Sep 17 00:00:00 2001 From: Precious29-web Date: Tue, 25 Nov 2025 06:54:37 +0200 Subject: [PATCH 15/31] dialysis HSI --- src/tlo/methods/cmd_chronic_kidney_disease.py | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index e46a292b35..bd088f9632 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -461,6 +461,39 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ckd_date_treatment'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] +class HSI_CardioMetabolicDisorders_Dialysis_Refill(HSI_Event, IndividualScopeEventMixin): + """This is a Health System Interaction Event in which a person receives a dialysis session 2 times a week + adding up to 8 times a month.""" + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + + self.TREATMENT_ID = 'CardioMetabolicDisorders_Treatment_Haemodialysis' + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) + self.ACCEPTED_FACILITY_LEVEL = '3' + + def apply(self, person_id, squeeze_factor): + + df = self.sim.population.props + + if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'nc_chronic_kidney_disease']: + return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + + # Increment total number of dialysis sessions the person has ever had in their lifetime + df.at[person_id, 'nc_ckd_total_dialysis_sessions'] += 1 + + + self.add_equipment({'Chair', 'Dialysis Machine', 'Dialyser (Artificial Kidney)', + 'Bloodlines', 'Dialysate solution', 'Dialysis water treatment system'}) + + next_session_date = self.sim.date + pd.DateOffset(days=3) + self.sim.modules['HealthSystem'].schedule_hsi_event(self, + topen=next_session_date, + tclose=next_session_date + pd.DateOffset(days=1), + priority=1 + ) + + class HSI_Kidney_Transplant_Surgery(HSI_Event, IndividualScopeEventMixin): """ This is the event a person undergoes in order to determine whether an individual is eligible for a kidney transplant From c0631b1caee3609265ebe46cf5df344d96fe07bd Mon Sep 17 00:00:00 2001 From: thewati Date: Tue, 25 Nov 2025 10:07:57 +0200 Subject: [PATCH 16/31] formatting --- src/tlo/methods/cmd_chronic_kidney_disease.py | 60 ++++--------------- 1 file changed, 11 insertions(+), 49 deletions(-) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index bd088f9632..c60fda3196 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -291,6 +291,7 @@ def look_up_consumable_item_codes(self): get_item_codes("Syringe, Autodisable SoloShot IX "): 1 } + class CMDChronicKidneyDiseasePollEvent(RegularEvent, PopulationScopeEventMixin): """An event that controls the development process of Chronic Kidney Disease (CKD) and logs current states.""" @@ -421,45 +422,7 @@ def apply(self, person_id, squeeze_factor): topen=next_session, tclose=None, priority=1) - # Define the necessary information for an HSI - #todo need to update priority number in resource files - self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Kidney_Transplant_' - self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' - self.ALERT_OTHER_DISEASES = [] - - - - def apply(self, person_id, squeeze_factor): - logger.debug(key='debug', - data=f'This is HSI_Kidney_Transplant_Evaluation for person {person_id}') - df = self.sim.population.props - person = df.loc[person_id] - hs = self.sim.modules["HealthSystem"] - if not df.at[person_id, 'is_alive']: - # The person is not alive, the event did not happen: so return a blank footprint - return self.sim.modules['HealthSystem'].get_blank_appt_footprint() - - # if person already on treatment or not yet diagnosed, do nothing - if person["ckd_on_treatment"] or not person["ckd_diagnosed"]: - return self.sim.modules["HealthSystem"].get_blank_appt_footprint() - - assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) - - is_cons_available = self.get_consumables( - self.module.cons_item_codes['kidney_transplant_eval_cons'] - ) - dx_result = hs.dx_manager.run_dx_test( - dx_tests_to_run='kidney_transplant_eval_tests', - hsi_event=self - ) - - if dx_result and is_cons_available: - # record date of diagnosis - df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date - df.at[person_id, 'ckd_date_treatment'] = self.sim.date - df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] class HSI_CardioMetabolicDisorders_Dialysis_Refill(HSI_Event, IndividualScopeEventMixin): """This is a Health System Interaction Event in which a person receives a dialysis session 2 times a week @@ -473,7 +436,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' def apply(self, person_id, squeeze_factor): - df = self.sim.population.props if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'nc_chronic_kidney_disease']: @@ -482,16 +444,15 @@ def apply(self, person_id, squeeze_factor): # Increment total number of dialysis sessions the person has ever had in their lifetime df.at[person_id, 'nc_ckd_total_dialysis_sessions'] += 1 - self.add_equipment({'Chair', 'Dialysis Machine', 'Dialyser (Artificial Kidney)', 'Bloodlines', 'Dialysate solution', 'Dialysis water treatment system'}) next_session_date = self.sim.date + pd.DateOffset(days=3) self.sim.modules['HealthSystem'].schedule_hsi_event(self, - topen=next_session_date, - tclose=next_session_date + pd.DateOffset(days=1), - priority=1 - ) + topen=next_session_date, + tclose=next_session_date + pd.DateOffset(days=1), + priority=1 + ) class HSI_Kidney_Transplant_Surgery(HSI_Event, IndividualScopeEventMixin): @@ -530,6 +491,11 @@ def apply(self, person_id, squeeze_factor): self.module.cons_item_codes['kidney_transplant_surgery_cons'] ) + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='kidney_transplant_surgery_tests', + hsi_event=self + ) + if dx_result and is_cons_available: self.add_equipment({'Patient monitors', 'Infusion pump', 'Dialysis machine', 'Bloodlines', 'Water tank', 'Reverse osmosis machine', 'Water softener', 'Carbon filter', '5 micro filter', @@ -537,17 +503,13 @@ def apply(self, person_id, squeeze_factor): 'cold static storage' 'perfusion machine', 'Ultrasound machine', 'drip stand', 'trolley', }) - dx_result = hs.dx_manager.run_dx_test( - dx_tests_to_run='kidney_transplant_surgery_tests', - hsi_event=self - ) - if dx_result and is_cons_available: # record date of diagnosis df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date df.at[person_id, 'ckd_date_treatment'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] + class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): """ This is the event a person undergoes in order to determine whether an individual is eligible for a kidney transplant From 7d0902c2b93ab2ed71b4c38aaf269b3ce84d975c Mon Sep 17 00:00:00 2001 From: thewati Date: Tue, 25 Nov 2025 10:30:04 +0200 Subject: [PATCH 17/31] Add never ran to Refill HSI --- src/tlo/methods/cmd_chronic_kidney_disease.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index c60fda3196..707cbdd62a 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -95,6 +95,8 @@ class CMDChronicKidneyDisease(Module): "uses_herbal_medicine": Property( Types.BOOL, "Whether this person uses herbal medicine" ), + "nc_ckd_total_dialysis_sessions": Property(Types.INT, + "total number of dialysis sessions the person has ever had"), } def __init__(self): @@ -152,11 +154,11 @@ def initialise_population(self, population: Population) -> None: df.loc[list(alive_ckd_idx), "ckd_date_treatment"] = pd.NaT df.loc[list(alive_ckd_idx), "ckd_stage_at_which_treatment_given"] = "pre_diagnosis" df.loc[list(alive_ckd_idx), "ckd_date_diagnosis"] = pd.NaT + df.loc[list(alive_ckd_idx), "nc_ckd_total_dialysis_sessions"] = 0 df.loc[list(alive_ckd_idx), "uses_herbal_medicine"] = \ self.rng.random(len(alive_ckd_idx)) < self.parameters['prop_herbal_use_ckd'] - df.loc[list(df.loc[df.is_alive & ~df.nc_chronic_kidney_disease].index), - "uses_herbal_medicine"] = False + df.loc[list(df.loc[df.is_alive & ~df.nc_chronic_kidney_disease].index), "uses_herbal_medicine"] = False # -------------------- ckd_status ----------- # Determine who has CKD at all stages: @@ -223,6 +225,7 @@ def on_birth(self, mother_id: int, child_id: int) -> None: self.sim.population.props.at[child_id, 'ckd_stage_at_which_treatment_given'] = 'pre_diagnosis' self.sim.population.props.at[child_id, 'ckd_diagnosed'] = False self.sim.population.props.at[child_id, 'ckd_date_diagnosis'] = pd.NaT + self.sim.population.props.at[child_id, 'nc_ckd_total_dialysis_sessions'] = 0 def on_simulation_end(self) -> None: pass @@ -380,7 +383,7 @@ def __init__(self, module, person_id): assert isinstance(module, CMDChronicKidneyDisease) # Define the necessary information for an HSI - self.TREATMENT_ID = 'Dr_CMD_Renal_Medication' + self.TREATMENT_ID = 'CKD_Renal_Medication' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' #todo Facility Level? self.ALERT_OTHER_DISEASES = [] @@ -431,7 +434,7 @@ class HSI_CardioMetabolicDisorders_Dialysis_Refill(HSI_Event, IndividualScopeEve def __init__(self, module, person_id): super().__init__(module, person_id=person_id) - self.TREATMENT_ID = 'CardioMetabolicDisorders_Treatment_Haemodialysis' + self.TREATMENT_ID = 'CKD_Treatment_Haemodialysis' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' @@ -454,6 +457,16 @@ def apply(self, person_id, squeeze_factor): priority=1 ) + def never_ran(self) -> None: + """What to do if the event is never run by the HealthSystem""" + # Reschedule this HSI to happen again 3 days time. + next_session_date = self.sim.date + pd.DateOffset(days=3) + self.sim.modules['HealthSystem'].schedule_hsi_event(self, + topen=next_session_date, + tclose=next_session_date + pd.DateOffset(days=1), + priority=1 + ) + class HSI_Kidney_Transplant_Surgery(HSI_Event, IndividualScopeEventMixin): """ @@ -466,7 +479,7 @@ def __init__(self, module, person_id): # Define the necessary information for an HSI #todo need to update priority number in resource files - self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Kidney_Transplant_' + self.TREATMENT_ID = 'CKD_Kidney_Transplant' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.ALERT_OTHER_DISEASES = [] @@ -520,7 +533,7 @@ def __init__(self, module, person_id): assert isinstance(module, CMDChronicKidneyDisease) # Define the necessary information for an HSI - self.TREATMENT_ID = 'Dr_CMD_Kidney_Transplant_Evaluation' + self.TREATMENT_ID = 'CKD_Kidney_Transplant_Evaluation' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.ALERT_OTHER_DISEASES = [] From ffa993287e0b337df1cd429fef0008aa707464fa Mon Sep 17 00:00:00 2001 From: thewati Date: Wed, 26 Nov 2025 09:46:25 +0200 Subject: [PATCH 18/31] queues, anti-rejection and test file --- src/tlo/methods/cmd_chronic_kidney_disease.py | 144 +++++++++--- tests/test_cmd_chronic_kidney_disease.py | 210 ++++++++++++++++++ 2 files changed, 329 insertions(+), 25 deletions(-) create mode 100644 tests/test_cmd_chronic_kidney_disease.py diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 707cbdd62a..3da4a175b9 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -5,6 +5,7 @@ import pandas as pd from tlo import DateOffset, Module, Parameter, Population, Property, Simulation, Types, logging +from collections import deque from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor from tlo.methods import Metadata @@ -13,6 +14,7 @@ # from tlo.methods.symptommanager import Symptom from tlo.population import IndividualProperties + logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -63,6 +65,13 @@ class CMDChronicKidneyDisease(Module): "rp_ckd_herbal_use_baseline": Parameter( Types.REAL, "Relative risk of having CKD at baseline if a person uses herbal medicine" ), + "prob_transplant_success": Parameter( + Types.REAL, "Probability that kidney transplant surgery is successful" + ), + "max_surgeries_per_month": Parameter( + Types.INT, + "Maximum number of kidney transplant surgeries that can be performed in a month" + ), } PROPERTIES = { @@ -102,6 +111,7 @@ class CMDChronicKidneyDisease(Module): def __init__(self): super().__init__() self.cons_item_codes = None # (Will store consumable item codes) + self.kidney_transplant_waiting_list = deque() def read_parameters(self, data_folder: str | Path) -> None: """ initialise module parameters. Here we are assigning values to all parameters defined at the beginning of @@ -128,6 +138,9 @@ def read_parameters(self, data_folder: str | Path) -> None: self.parameters['prob_ckd_transplant_eval'] = 0.3 self.parameters['prop_herbal_use_ckd'] = 0.35 + self.parameters['prob_transplant_success'] = 0.8 + self.parameters['max_surgeries_per_month'] = 3 + #todo use chronic_kidney_disease_symptoms from cardio_metabolic_disorders.py # self.sim.modules['SymptomManager'].register_symptom( @@ -266,7 +279,7 @@ def look_up_consumable_item_codes(self): get_item_codes('Radiopharmaceuticals'): 4 } - self.cons_item_codes['Kidney_Transplant_Surgery'] = { + self.cons_item_codes['kidney_transplant_surgery_cons'] = { # Prepare surgical instruments # administer an IV get_item_codes('Cannula iv (winged with injection pot) 18_each_CMST'): 1, @@ -345,7 +358,7 @@ def apply(self, population: Population) -> None: size=len(eligible_idx), p=probs ) - + #todo stage1_4 should go to renal_clinic and stage5 should go to transplant_eval and/or haemodialysis for person_id, hsi_choice in zip(eligible_idx, hsi_choices): if hsi_choice == 'renal_clinic': self.sim.modules['HealthSystem'].schedule_hsi_event( @@ -362,6 +375,29 @@ def apply(self, population: Population) -> None: tclose=self.sim.date + DateOffset(months=1), ) + surgeries_done = 0 + + while ( + surgeries_done < self.module.parameters['max_surgeries_per_month'] + and len(self.module.kidney_transplant_waiting_list) > 0 + ): + next_person = self.module.kidney_transplant_waiting_list.popleft() + + # Skip if dead + if not population.props.at[next_person, 'is_alive']: + continue + + # Schedule the transplant + self.sim.modules["HealthSystem"].schedule_hsi_event( + HSI_Kidney_Transplant_Surgery(self.module, next_person), + priority=1, + topen=self.sim.date, + tclose=self.sim.date + DateOffset(months=1) + ) + # increments until max_surgeries_per_month is reached + surgeries_done += 1 + + def do_at_generic_first_appt( self, person_id: int, @@ -427,7 +463,7 @@ def apply(self, person_id, squeeze_factor): priority=1) -class HSI_CardioMetabolicDisorders_Dialysis_Refill(HSI_Event, IndividualScopeEventMixin): +class HSI_Haemodialysis_Refill(HSI_Event, IndividualScopeEventMixin): """This is a Health System Interaction Event in which a person receives a dialysis session 2 times a week adding up to 8 times a month.""" @@ -468,7 +504,8 @@ def never_ran(self) -> None: ) -class HSI_Kidney_Transplant_Surgery(HSI_Event, IndividualScopeEventMixin): + +class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): """ This is the event a person undergoes in order to determine whether an individual is eligible for a kidney transplant """ @@ -478,15 +515,14 @@ def __init__(self, module, person_id): assert isinstance(module, CMDChronicKidneyDisease) # Define the necessary information for an HSI - #todo need to update priority number in resource files - self.TREATMENT_ID = 'CKD_Kidney_Transplant' + self.TREATMENT_ID = 'CKD_Kidney_Transplant_Evaluation' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.ALERT_OTHER_DISEASES = [] def apply(self, person_id, squeeze_factor): logger.debug(key='debug', - data=f'This is HSI_Kidney_Transplant_surgery for person {person_id}') + data=f'This is HSI_Kidney_Transplant_Evaluation for person {person_id}') df = self.sim.population.props person = df.loc[person_id] hs = self.sim.modules["HealthSystem"] @@ -501,29 +537,33 @@ def apply(self, person_id, squeeze_factor): assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) is_cons_available = self.get_consumables( - self.module.cons_item_codes['kidney_transplant_surgery_cons'] + self.module.cons_item_codes['kidney_transplant_eval_cons'] ) dx_result = hs.dx_manager.run_dx_test( - dx_tests_to_run='kidney_transplant_surgery_tests', + dx_tests_to_run='kidney_transplant_eval_tests', hsi_event=self ) - if dx_result and is_cons_available: - self.add_equipment({'Patient monitors', 'Infusion pump', 'Dialysis machine', 'Bloodlines', 'Water tank', - 'Reverse osmosis machine', 'Water softener', 'Carbon filter', '5 micro filter', - 'ventilator', 'Electrocautery unit', 'Suction machine', 'theatre bed', - 'cold static storage' 'perfusion machine', 'Ultrasound machine', 'drip stand', - 'trolley', }) - if dx_result and is_cons_available: # record date of diagnosis df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date df.at[person_id, 'ckd_date_treatment'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] + # Append to waiting list if treatment is successful + self.module.kidney_transplant_waiting_list.append(person_id) -class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): + else: + hs.schedule_hsi_event( + HSI_Haemodialysis_Refill(self.module, person_id), + topen=self.sim.date, + tclose=self.sim.date + pd.DateOffset(days=1), + priority=1 + ) + + +class HSI_Kidney_Transplant_Surgery(HSI_Event, IndividualScopeEventMixin): """ This is the event a person undergoes in order to determine whether an individual is eligible for a kidney transplant """ @@ -533,14 +573,15 @@ def __init__(self, module, person_id): assert isinstance(module, CMDChronicKidneyDisease) # Define the necessary information for an HSI - self.TREATMENT_ID = 'CKD_Kidney_Transplant_Evaluation' + # todo need to update priority number in resource files + self.TREATMENT_ID = 'CKD_Kidney_Transplant' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.ALERT_OTHER_DISEASES = [] def apply(self, person_id, squeeze_factor): logger.debug(key='debug', - data=f'This is HSI_Kidney_Transplant_Evaluation for person {person_id}') + data=f'This is HSI_Kidney_Transplant_Surgery for person {person_id}') df = self.sim.population.props person = df.loc[person_id] hs = self.sim.modules["HealthSystem"] @@ -555,19 +596,72 @@ def apply(self, person_id, squeeze_factor): assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) is_cons_available = self.get_consumables( - self.module.cons_item_codes['kidney_transplant_eval_cons'] + self.module.cons_item_codes['kidney_transplant_surgery_cons'] ) dx_result = hs.dx_manager.run_dx_test( - dx_tests_to_run='kidney_transplant_eval_tests', + dx_tests_to_run='kidney_transplant_surgery_tests', hsi_event=self ) + # Check if transplant is successful + transplant_successful = self.module.rng.random() < self.module.parameters['prob_transplant_success'] + if dx_result and is_cons_available: - # record date of diagnosis - df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date - df.at[person_id, 'ckd_date_treatment'] = self.sim.date - df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] + self.add_equipment({'Patient monitors', 'Infusion pump', 'Dialysis machine', 'Bloodlines', 'Water tank', + 'Reverse osmosis machine', 'Water softener', 'Carbon filter', '5 micro filter', + 'ventilator', 'Electrocautery unit', 'Suction machine', 'theatre bed', + 'cold static storage' 'perfusion machine', 'Ultrasound machine', 'drip stand', + 'trolley', }) + + if transplant_successful: + # df.at[person_id, 'ckd_transplant_successful'] = True + df.at[person_id, 'ckd_date_transplant'] = self.sim.date + # df.at[person_id, 'ckd_on_anti_rejection_drugs'] = True + # df.at[person_id, 'ckd_on_transplant_waiting_list'] = False + # df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] + + # Schedule monthly anti-rejection drug refill + next_month = self.sim.date + pd.DateOffset(months=1) + hs.schedule_hsi_event( + HSI_AntiRejectionDrug_Refill(self.module, person_id), + topen=next_month, + tclose=next_month + pd.DateOffset(days=5), + priority=1 + ) + + else: + df.at[person_id, 'ckd_date_transplant'] = self.sim.date + + self.sim.modules['Demography'].do_death( + individual_id=person_id, + cause='chronic_kidney_disease_death', + originating_module=self.module + ) + + +class HSI_AntiRejectionDrug_Refill(HSI_Event, IndividualScopeEventMixin): + def __init__(self, module, person_id): + super().__init__(module, person_id) + self.TREATMENT_ID = 'CKD_AntiRejectionDrug_Refill' + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) + self.ACCEPTED_FACILITY_LEVEL = '2' + + def apply(self, person_id, squeeze_factor): + df = self.sim.population.props + + if not df.at[person_id, 'is_alive']: + return self.sim.modules["HealthSystem"].get_blank_appt_footprint() + + # Person stays on anti-rejection drugs for life + next_month = self.sim.date + pd.DateOffset(months=1) + self.sim.modules["HealthSystem"].schedule_hsi_event( + self, + topen=next_month, + tclose=next_month + pd.DateOffset(days=5), + priority=1 + ) + class CMDChronicKidneyDiseaseLoggingEvent(RegularEvent, PopulationScopeEventMixin): diff --git a/tests/test_cmd_chronic_kidney_disease.py b/tests/test_cmd_chronic_kidney_disease.py new file mode 100644 index 0000000000..146017135e --- /dev/null +++ b/tests/test_cmd_chronic_kidney_disease.py @@ -0,0 +1,210 @@ +import os +from pathlib import Path + +import pandas as pd +import pytest + +from tlo import Date, Simulation +from tlo.methods import ( + cardio_metabolic_disorders, + cmd_chronic_kidney_disease, + demography, + depression, + enhanced_lifestyle, + healthburden, + healthseekingbehaviour, + healthsystem, + simplified_births, + symptommanager, +) + +try: + resourcefilepath = Path(os.path.dirname(__file__)) / '../resources' +except NameError: + # running interactively + resourcefilepath = Path('./resources') + +start_date = Date(2010, 1, 1) +end_date = Date(2010, 1, 2) +popsize = 1000 + + +def get_simulation(seed): + """Return simulation objection with cmd_chronic_kidney_disease and other necessary modules registered.""" + sim = Simulation( + start_date=start_date, + seed=seed, + resourcefilepath=resourcefilepath + ) + + sim.register(demography.Demography(), + simplified_births.SimplifiedBirths(), + enhanced_lifestyle.Lifestyle(), + healthsystem.HealthSystem(disable=False, cons_availability='all'), + symptommanager.SymptomManager(), + healthseekingbehaviour.HealthSeekingBehaviour( # force symptoms to lead to health care seeking: + force_any_symptom_to_lead_to_healthcareseeking=True + ), + healthburden.HealthBurden(), + cardio_metabolic_disorders.CardioMetabolicDisorders(), + cmd_chronic_kidney_disease.CMDChronicKidneyDisease(), + depression.Depression(), + ) + return sim + + +def get_simulation_healthsystemdisabled(seed): + """Make the simulation with the health system disabled + """ + sim = Simulation( + start_date=start_date, + seed=seed, + resourcefilepath=resourcefilepath + ) + + # Register the appropriate modules + sim.register(demography.Demography(), + simplified_births.SimplifiedBirths(), + enhanced_lifestyle.Lifestyle(), + healthsystem.HealthSystem(disable=True), + symptommanager.SymptomManager(), + healthseekingbehaviour.HealthSeekingBehaviour( # force symptoms to lead to health care seeking: + force_any_symptom_to_lead_to_healthcareseeking=False + ), + healthburden.HealthBurden(), + cardio_metabolic_disorders.CardioMetabolicDisorders(), + cmd_chronic_kidney_disease.CMDChronicKidneyDisease(), + depression.Depression(), + ) + return sim + + +def get_simulation_nohsi(seed): + """Make the simulation with: + * the healthsystem enabled but with no service availability (so no HSI run) + """ + sim = Simulation(start_date=start_date, seed=seed, resourcefilepath=resourcefilepath) + + # Register the appropriate modules + sim.register(demography.Demography(), + simplified_births.SimplifiedBirths(), + enhanced_lifestyle.Lifestyle(), + healthsystem.HealthSystem(service_availability=[]), + symptommanager.SymptomManager(), + healthseekingbehaviour.HealthSeekingBehaviour(), + healthburden.HealthBurden(), + cardio_metabolic_disorders.CardioMetabolicDisorders(), + cmd_chronic_kidney_disease.CMDChronicKidneyDisease(), + depression.Depression() + ) + return sim + + +def check_dtypes(sim): + df = sim.population.props + orig = sim.population.new_row + assert (df.dtypes == orig.dtypes).all() + + +def zero_out_init_prev(sim): + # Set initial prevalence to zero: + sim.modules['CMDChronicKidneyDisease'].parameters['init_prob_any_ckd'] = [0.0] * 2 + return sim + + +def make_high_init_prev(sim): + # Set initial prevalence to a high value: + sim.modules['CMDChronicKidneyDisease'].parameters['init_prob_any_ckd'] = [0.1] * 2 + return sim + + +def incr_rate_of_onset_stage1_4(sim): + # Rate of CKD onset per # months: + # sim.modules['CMDChronicKidneyDisease'].parameters['rate_onset_to_stage1_4'] = 0.05 + sim.modules['CMDChronicKidneyDisease'].parameters['rate_onset_to_stage1_4'] = 0.5 + return sim + + +def zero_rate_of_onset_stage1_4(sim): + # Rate of CKD onset per # months: + sim.modules['CMDChronicKidneyDisease'].parameters['rate_onset_to_stage1_4'] = 0.00 + return sim + + +def incr_rates_of_progression(sim): + # Rates of CKD progression: + sim.modules['CMDChronicKidneyDisease'].parameters['rate_onset_to_stage1_4'] *= 5 + sim.modules['CMDChronicKidneyDisease'].parameters['rate_stage1_4_to_stage5'] *= 5 + return sim + + +def check_configuration_of_population(sim): + df = sim.population.props.copy() + + # Boolean for any stage of CKD + df['ckd_status_any_stage'] = df.ckd_status != 'pre_diagnosis' + + # Get alive people: + df = df.loc[df.is_alive] + + # check that diagnosis and treatment is never applied to someone who has never had CKD + assert pd.isnull(df.loc[df.ckd_status == 'pre_diagnosis', 'ckd_date_diagnosis']).all() + assert pd.isnull(df.loc[df.ckd_status == 'pre_diagnosis', 'ckd_date_treatment']).all() + + # check that those diagnosed are a subset of those with any CKD (and that the date makes sense): + assert set(df.index[~pd.isnull(df.ckd_date_diagnosis)]).issubset(df.index[df.ckd_status_any_stage]) + + +@pytest.mark.slow +def test_basic_run(seed): + """Run the simulation with the cmd_chronic_kidney_disease module and read the log from the + cmd_chronic_kidney_disease module.""" + sim = get_simulation(seed) + sim.make_initial_population(n=popsize) + + check_dtypes(sim) + sim.simulate(end_date=Date(2010, 5, 1)) + check_dtypes(sim) + + +def test_initial_config_of_pop_usual_prevalence(seed): + """Tests the way the population is configured: with usual initial prevalence values""" + sim = get_simulation_healthsystemdisabled(seed=seed) + sim.make_initial_population(n=popsize) + check_dtypes(sim) + check_configuration_of_population(sim) + + +def test_initial_config_of_pop_zero_prevalence(seed): + """Tests the way the population is configured: with zero initial prevalence values """ + sim = get_simulation_healthsystemdisabled(seed=seed) + sim = zero_out_init_prev(sim) + sim.make_initial_population(n=popsize) + check_dtypes(sim) + check_configuration_of_population(sim) + df = sim.population.props + assert (df.loc[df.is_alive & ~df.nc_chronic_kidney_disease].ckd_status == 'pre_diagnosis').all() + + +def test_initial_config_of_pop_high_prevalence(seed): + """Tests the way the population is configured: with high initial prevalence values """ + sim = get_simulation_healthsystemdisabled(seed=seed) + sim = make_high_init_prev(sim) + sim.make_initial_population(n=popsize) + check_dtypes(sim) + check_configuration_of_population(sim) + + +@pytest.mark.slow +def test_run_sim_from_high_prevalence(seed): + """Run the simulation from the usual prevalence values and high rates of incidence and check configuration of + properties at the end""" + sim = get_simulation_healthsystemdisabled(seed=seed) + sim = make_high_init_prev(sim) + sim = incr_rates_of_progression(sim) + sim.make_initial_population(n=popsize) + check_dtypes(sim) + check_configuration_of_population(sim) + sim.simulate(end_date=Date(2010, 4, 1)) + check_dtypes(sim) + check_configuration_of_population(sim) From 4ac18841f9e07443c943b890ce5566407b0968bf Mon Sep 17 00:00:00 2001 From: thewati Date: Wed, 26 Nov 2025 15:08:15 +0200 Subject: [PATCH 19/31] stage5 dalys --- src/tlo/methods/cmd_chronic_kidney_disease.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 3da4a175b9..22550e2a8e 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -14,7 +14,6 @@ # from tlo.methods.symptommanager import Symptom from tlo.population import IndividualProperties - logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -112,6 +111,7 @@ def __init__(self): super().__init__() self.cons_item_codes = None # (Will store consumable item codes) self.kidney_transplant_waiting_list = deque() + self.daly_wts = dict() def read_parameters(self, data_folder: str | Path) -> None: """ initialise module parameters. Here we are assigning values to all parameters defined at the beginning of @@ -225,8 +225,26 @@ def initialise_simulation(self, sim: Simulation) -> None: self.make_the_linear_models() self.look_up_consumable_item_codes() - def report_daly_values(self) -> pd.Series: - return pd.Series(index=self.sim.population.props.index, data=0.0) + # ----- DISABILITY-WEIGHTS ----- + if "HealthBurden" in self.sim.modules: + # For those with End-stage renal disease, with kidney transplant (any stage after stage1_4) + self.daly_wts["stage5_ckd"] = self.sim.modules["HealthBurden"].get_daly_weight( + sequlae_code=977 + ) + + def report_daly_values(self): + # return pd.Series(index=self.sim.population.props.index, data=0.0) + df = self.sim.population.props # shortcut to population properties dataframe for alive persons + + disability_series_for_alive_persons = pd.Series(index=df.index[df.is_alive], data=0.0) + + # Assign daly_wt to those with CKD stage stage5 and have had a kidney transplant + disability_series_for_alive_persons.loc[ + (df.ckd_status == "stage5") & + (~pd.isnull(df.ckd_date_transplant)) + ] = self.daly_wts['stage5_ckd'] + + return disability_series_for_alive_persons def on_birth(self, mother_id: int, child_id: int) -> None: """ Set properties of a child when they are born. @@ -397,7 +415,6 @@ def apply(self, population: Population) -> None: # increments until max_surgeries_per_month is reached surgeries_done += 1 - def do_at_generic_first_appt( self, person_id: int, @@ -504,7 +521,6 @@ def never_ran(self) -> None: ) - class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): """ This is the event a person undergoes in order to determine whether an individual is eligible for a kidney transplant @@ -663,7 +679,6 @@ def apply(self, person_id, squeeze_factor): ) - class CMDChronicKidneyDiseaseLoggingEvent(RegularEvent, PopulationScopeEventMixin): """The only logging event for this module""" From a8e8338b8ad6cfbcb806a9ff10a26f417de55f92 Mon Sep 17 00:00:00 2001 From: Precious29-web Date: Thu, 27 Nov 2025 00:41:52 +0200 Subject: [PATCH 20/31] Improving docstrings --- src/tlo/methods/cmd_chronic_kidney_disease.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 22550e2a8e..0edc2aba61 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -427,9 +427,7 @@ def do_at_generic_first_appt( class HSI_Renal_Clinic_and_Medication(HSI_Event, IndividualScopeEventMixin): """ - This is the event when a person undergoes the optical coherence topography before being given the anti-vegf - injection. Given to individuals with dr_status of severe and proliferative - """ + This is an event where a CKD patient is managed conservatively; attending clinic monthly for symptom management""" def __init__(self, module, person_id): super().__init__(module, person_id=person_id) @@ -481,7 +479,7 @@ def apply(self, person_id, squeeze_factor): class HSI_Haemodialysis_Refill(HSI_Event, IndividualScopeEventMixin): - """This is a Health System Interaction Event in which a person receives a dialysis session 2 times a week + """This is an event in which a person goes for dialysis sessions 2 times a week adding up to 8 times a month.""" def __init__(self, module, person_id): @@ -657,6 +655,8 @@ def apply(self, person_id, squeeze_factor): class HSI_AntiRejectionDrug_Refill(HSI_Event, IndividualScopeEventMixin): + """This is an event where a kidney transplant recipient gets drugs every month for the rest of their lives """ + def __init__(self, module, person_id): super().__init__(module, person_id) self.TREATMENT_ID = 'CKD_AntiRejectionDrug_Refill' From d6588d0d88dc51c773e3485aba563369b12e97f9 Mon Sep 17 00:00:00 2001 From: thewati Date: Fri, 5 Dec 2025 14:23:56 +0200 Subject: [PATCH 21/31] cdk resource file created --- .../parameter_values.csv | 3 + src/tlo/methods/cmd_chronic_kidney_disease.py | 91 ++++++++++--------- 2 files changed, 49 insertions(+), 45 deletions(-) create mode 100644 resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv diff --git a/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv b/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv new file mode 100644 index 0000000000..67e8f3801d --- /dev/null +++ b/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cef12de1ce6f7c3eac4f2a7c1b729b330988d4cec6f39f3c567a9df6de2055c5 +size 696 diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 0edc2aba61..8600feac62 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -1,4 +1,6 @@ from pathlib import Path +from typing import Optional + # from typing import Union import numpy as np @@ -13,6 +15,7 @@ from tlo.methods.hsi_generic_first_appts import HSIEventScheduler # from tlo.methods.symptommanager import Symptom from tlo.population import IndividualProperties +from tlo.util import read_csv_files logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -105,6 +108,8 @@ class CMDChronicKidneyDisease(Module): ), "nc_ckd_total_dialysis_sessions": Property(Types.INT, "total number of dialysis sessions the person has ever had"), + "nc_ckd_on_dialysis": Property(Types.BOOL, + "Whether this person is on dialysis"), } def __init__(self): @@ -113,33 +118,13 @@ def __init__(self): self.kidney_transplant_waiting_list = deque() self.daly_wts = dict() - def read_parameters(self, data_folder: str | Path) -> None: - """ initialise module parameters. Here we are assigning values to all parameters defined at the beginning of - this module. - - :param data_folder: Path to the folder containing parameter values - - """ - # TODO Read from resourcefile - self.parameters['rate_onset_to_stage1_4'] = 0.29 - self.parameters['rate_stage1_4_to_stage5'] = 0.4 - - self.parameters['init_prob_any_ckd'] = [0.6, 0.4] - - self.parameters['rp_ckd_nc_diabetes'] = 1.1 - self.parameters['rp_ckd_hiv_infection'] = 1.2 - self.parameters['rp_ckd_li_bmi'] = 1.3 - self.parameters['rp_ckd_nc_hypertension'] = 1.3 - self.parameters['rp_ckd_nc_chronic_ischemic_hd'] = 1.2 - self.parameters['rp_ckd_herbal_use_baseline'] = 1.35 - - # - self.parameters['prob_ckd_renal_clinic'] = 0.7 - self.parameters['prob_ckd_transplant_eval'] = 0.3 - self.parameters['prop_herbal_use_ckd'] = 0.35 + def read_parameters(self, resourcefilepath: Optional[Path] = None): + """Setup parameters used by the module""" - self.parameters['prob_transplant_success'] = 0.8 - self.parameters['max_surgeries_per_month'] = 3 + self.load_parameters_from_dataframe( + read_csv_files(resourcefilepath / "ResourceFile_CMD_Chronic_Kidney_Disease", + files="parameter_values") + ) #todo use chronic_kidney_disease_symptoms from cardio_metabolic_disorders.py @@ -155,7 +140,7 @@ def initialise_population(self, population: Population) -> None: """ df = population.props - # p = self.parameters + p = self.parameters alive_ckd_idx = df.loc[df.is_alive & df.nc_chronic_kidney_disease].index @@ -168,25 +153,26 @@ def initialise_population(self, population: Population) -> None: df.loc[list(alive_ckd_idx), "ckd_stage_at_which_treatment_given"] = "pre_diagnosis" df.loc[list(alive_ckd_idx), "ckd_date_diagnosis"] = pd.NaT df.loc[list(alive_ckd_idx), "nc_ckd_total_dialysis_sessions"] = 0 + df.loc[list(alive_ckd_idx), "nc_ckd_on_dialysis"] = False df.loc[list(alive_ckd_idx), "uses_herbal_medicine"] = \ - self.rng.random(len(alive_ckd_idx)) < self.parameters['prop_herbal_use_ckd'] + self.rng.random(len(alive_ckd_idx)) < p['prop_herbal_use_ckd'] df.loc[list(df.loc[df.is_alive & ~df.nc_chronic_kidney_disease].index), "uses_herbal_medicine"] = False # -------------------- ckd_status ----------- # Determine who has CKD at all stages: # check parameters are sensible: probability of having any CKD stage cannot exceed 1.0 - assert sum(self.parameters['init_prob_any_ckd']) <= 1.0 + assert sum(p['init_prob_any_ckd']) <= 1.0 lm_init_ckd_status_any_ckd = LinearModel( LinearModelType.MULTIPLICATIVE, - sum(self.parameters['init_prob_any_ckd']), - Predictor('nc_diabetes').when(True, self.parameters['rp_ckd_nc_diabetes']), - Predictor('hv_inf').when(True, self.parameters['rp_ckd_hiv_infection']), - Predictor('li_bmi').when(True, self.parameters['rp_ckd_li_bmi']), - Predictor('nc_hypertension').when(True, self.parameters['rp_ckd_nc_hypertension']), - Predictor('nc_chronic_ischemic_hd').when(True, self.parameters['rp_ckd_nc_chronic_ischemic_hd']), - Predictor('uses_herbal_medicine').when(True, self.parameters['rp_ckd_herbal_use_baseline']), + sum(p['init_prob_any_ckd']), + Predictor('nc_diabetes').when(True, p['rp_ckd_nc_diabetes']), + Predictor('hv_inf').when(True, p['rp_ckd_hiv_infection']), + Predictor('li_bmi').when(True, p['rp_ckd_li_bmi']), + Predictor('nc_hypertension').when(True, p['rp_ckd_nc_hypertension']), + Predictor('nc_chronic_ischemic_hd').when(True, p['rp_ckd_nc_chronic_ischemic_hd']), + Predictor('uses_herbal_medicine').when(True, p['rp_ckd_herbal_use_baseline']), ) @@ -201,11 +187,11 @@ def initialise_population(self, population: Population) -> None: categories = [cat for cat in df.ckd_status.cat.categories if cat != 'pre_diagnosis'] # Verify probabilities match categories - assert len(categories) == len(self.parameters['init_prob_any_ckd']) + assert len(categories) == len(p['init_prob_any_ckd']) # Normalize probabilities - total_prob = sum(self.parameters['init_prob_any_ckd']) - probs = [p / total_prob for p in self.parameters['init_prob_any_ckd']] + total_prob = sum(p['init_prob_any_ckd']) + probs = [p / total_prob for p in p['init_prob_any_ckd']] # Assign CKD stages df.loc[ckd_idx, 'ckd_status'] = self.rng.choice( @@ -228,9 +214,12 @@ def initialise_simulation(self, sim: Simulation) -> None: # ----- DISABILITY-WEIGHTS ----- if "HealthBurden" in self.sim.modules: # For those with End-stage renal disease, with kidney transplant (any stage after stage1_4) - self.daly_wts["stage5_ckd"] = self.sim.modules["HealthBurden"].get_daly_weight( + self.daly_wts["stage5_ckd_with_transplant"] = self.sim.modules["HealthBurden"].get_daly_weight( sequlae_code=977 ) + self.daly_wts["stage5_ckd_on_dialysis"] = self.sim.modules["HealthBurden"].get_daly_weight( + sequlae_code=987 + ) def report_daly_values(self): # return pd.Series(index=self.sim.population.props.index, data=0.0) @@ -242,7 +231,13 @@ def report_daly_values(self): disability_series_for_alive_persons.loc[ (df.ckd_status == "stage5") & (~pd.isnull(df.ckd_date_transplant)) - ] = self.daly_wts['stage5_ckd'] + ] = self.daly_wts['stage5_ckd_with_transplant'] + + # Assign daly_wt to those with CKD stage stage5 and are on dialysis + disability_series_for_alive_persons.loc[ + (df.ckd_status == "stage5") & + df.nc_ckd_on_dialysis + ] = self.daly_wts['stage5_ckd_on_dialysis'] return disability_series_for_alive_persons @@ -257,6 +252,7 @@ def on_birth(self, mother_id: int, child_id: int) -> None: self.sim.population.props.at[child_id, 'ckd_diagnosed'] = False self.sim.population.props.at[child_id, 'ckd_date_diagnosis'] = pd.NaT self.sim.population.props.at[child_id, 'nc_ckd_total_dialysis_sessions'] = 0 + self.sim.population.props.at[child_id, 'nc_ckd_on_dialysis'] = False def on_simulation_end(self) -> None: pass @@ -264,17 +260,18 @@ def on_simulation_end(self) -> None: def make_the_linear_models(self) -> None: """Make and save LinearModels that will be used when the module is running""" self.lm = dict() + p = self.parameters self.lm['onset_stage1_4'] = LinearModel( LinearModelType.MULTIPLICATIVE, - self.parameters['rate_onset_to_stage1_4'], + p['rate_onset_to_stage1_4'], Predictor('uses_herbal_medicine') - .when(True, self.parameters['rp_ckd_herbal_use_baseline']) + .when(True, p['rp_ckd_herbal_use_baseline']) ) self.lm['stage1_to_4_stage5'] = LinearModel( LinearModelType.MULTIPLICATIVE, - self.parameters['rate_stage1_4_to_stage5'], + p['rate_stage1_4_to_stage5'], Predictor('had_treatment_during_this_stage', external=True) .when(True, 0.0).otherwise(1.0) ) @@ -495,6 +492,9 @@ def apply(self, person_id, squeeze_factor): if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'nc_chronic_kidney_disease']: return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + if not df.at[person_id, 'nc_ckd_on_dialysis']: + df.at[person_id, 'nc_ckd_on_dialysis'] = True + # Increment total number of dialysis sessions the person has ever had in their lifetime df.at[person_id, 'nc_ckd_total_dialysis_sessions'] += 1 @@ -631,6 +631,7 @@ def apply(self, person_id, squeeze_factor): if transplant_successful: # df.at[person_id, 'ckd_transplant_successful'] = True df.at[person_id, 'ckd_date_transplant'] = self.sim.date + df.at[person_id, 'nc_ckd_on_dialysis'] = False # df.at[person_id, 'ckd_on_anti_rejection_drugs'] = True # df.at[person_id, 'ckd_on_transplant_waiting_list'] = False # df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] @@ -645,7 +646,7 @@ def apply(self, person_id, squeeze_factor): ) else: - df.at[person_id, 'ckd_date_transplant'] = self.sim.date + df.at[person_id, 'ckd_date_transplant'] = self.sim.date #todo Upile look at this logic, related to DALYs self.sim.modules['Demography'].do_death( individual_id=person_id, From eb997feb851696da2a45a761f5bbfba9f4d28794 Mon Sep 17 00:00:00 2001 From: thewati Date: Tue, 9 Dec 2025 15:11:27 +0200 Subject: [PATCH 22/31] ckd staging HSI, referalls, resource files --- .../parameter_values.csv | 4 +- ...rceFile_Consumables_Items_and_Packages.csv | 4 +- .../ResourceFile_EquipmentCatalogue.csv | 4 +- ...eFile_Equipment_Availability_Estimates.csv | 4 +- .../CVD.csv | 4 +- .../ClinicallyVulnerable.csv | 4 +- .../Default.csv | 4 +- .../EHP_III.csv | 4 +- .../LCOA_EHP.csv | 4 +- .../Naive.csv | 4 +- .../RMNCH.csv | 4 +- .../Test Mode 1.csv | 4 +- .../Test.csv | 4 +- .../VerticalProgrammes.csv | 4 +- src/tlo/methods/cmd_chronic_kidney_disease.py | 357 ++++++++++++------ tests/test_cmd_chronic_kidney_disease.py | 14 +- 16 files changed, 281 insertions(+), 146 deletions(-) diff --git a/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv b/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv index 67e8f3801d..22315e32df 100644 --- a/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv +++ b/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cef12de1ce6f7c3eac4f2a7c1b729b330988d4cec6f39f3c567a9df6de2055c5 -size 696 +oid sha256:3c91712951c3d75e2e76ceaeabc7f9e6d9fcd15909382b965aa9a289db5b04b0 +size 1005 diff --git a/resources/healthsystem/consumables/ResourceFile_Consumables_Items_and_Packages.csv b/resources/healthsystem/consumables/ResourceFile_Consumables_Items_and_Packages.csv index 0ee403abb0..4d78421ad7 100644 --- a/resources/healthsystem/consumables/ResourceFile_Consumables_Items_and_Packages.csv +++ b/resources/healthsystem/consumables/ResourceFile_Consumables_Items_and_Packages.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4106c2e3ae068d40b115857885b673bec3e1114be5183c0a4ae0366560e2a5c9 -size 249391 +oid sha256:1ba667ec65d5b4a2dd5fe936ad4f92c30bd714c5834f9b49c8586f98411e4920 +size 246826 diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv index d79bdc2682..9151dde016 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9e76003a2d742902f067636f16720289c02f8ab1bf763cb66c746734608e6109 -size 217 +oid sha256:c3ab7e1378993141bab2b0eaefd445335e81695e7588299740589c311fdb4426 +size 38850 diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment_Availability_Estimates.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment_Availability_Estimates.csv index 706297da67..5e0f8219ae 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment_Availability_Estimates.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment_Availability_Estimates.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2785365d20a4da4c147ba6a5df9e0259c9076df0fec556086aea0f2a068c9c53 -size 1313098 +oid sha256:6d497ccffc7a0cba6588ded2cb86e2a0d211b56bd49526dbd94e173a19e6bb19 +size 1325833 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv index c14cccf83d..4902830257 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be19c64d84ed49ae79be28f6a8e7abb8e279f7cb441ee90d934753731c708e12 -size 4138 +oid sha256:c46b74b4e1b5f4caa31c677bd3188bab4e6c81666629b37e7d0d3569144f44ab +size 4460 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv index ce5cf028a8..34d132e99e 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:824c5882bf4f320e58e7cff622fd5abf5c2b581fd3ed4f2df2d8a2f919509cfd -size 3742 +oid sha256:260f6a758d1cc6f46cf1b4d40da0b93147773903cd58e6537676d239a693a9c0 +size 4064 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv index 7a0dbc9d95..7b28e72052 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b87874bafc8f792822ef1962070c62aead74992e7856a7ca749a05b340eb20a -size 4140 +oid sha256:04d37f90aaa57f0d173521b6870e5a0ec168e62f8857b93e118723774025c779 +size 4462 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv index 4425679ae9..ca75c4c729 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f97a75bf186809ed5edc41f4542e61bf5ebefb131d7617bbccd74ef5b5a4f33b -size 4139 +oid sha256:90376b05a6c52c25327a1f5739bf9fda60c4c69f42ca6200016278435af9d517 +size 4461 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv index 3403c49229..69a0232c81 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50502e603cb50e6ea00f6df494b4f0cd39f216ce1dffff93ad9d136cd4592c50 -size 4138 +oid sha256:6e381646f7d5101380b37aabc415a1e16c3b65896e56ed9d8c97372e7f7b701f +size 4460 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv index 1f4d00fba8..54dad6827d 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c914b33a927d488ff650e7a39a1ba8e7b965c8cda73348595e74c2bb2a5fcf89 -size 4138 +oid sha256:a61a2b68e99596e47f0122b38185d417f5f371f177155756b88971ccba9fab9a +size 4460 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv index 5f753fabab..7098a64543 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:838b3ccc7b5882631ae455634aa8d034065a928a63f5ecd412176556738e20b9 -size 4118 +oid sha256:8b6d60f17b8aecb5b55ecaf7cb359fdec2f1c72a858c14fbfe4013b7bc17b0a7 +size 4440 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv index 8e745a1cb0..dbd27ddfb8 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38a678d31cacc4cff04f54d9a916cd5ff2f5f76641ac7bad316087056b8df16c -size 4140 +oid sha256:9a8718ace688a86e827b662a325ed2a521dc5733eaca32462d6734b68c7a674f +size 4462 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv index 7e0fd99721..ebeea61413 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4645d100a2b19ca1b50888d2233f7752d360763eb53ec9e61e4355c411b5f18d -size 4140 +oid sha256:c2336b46b4ea69b371a0b533ad9fd0b2799de2a943602f63cb3732f25198a51f +size 4462 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv index 39e26b7a1d..6027dccad9 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:191d303273c80fa7a1fe59488b5c63e718b9f92911ab45f70ada1c98db06e086 -size 4138 +oid sha256:593902b7987d7ff963dcbc4f37d8b703d47c8186f3b0efeb72a45f6de021d743 +size 4460 diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 8600feac62..b3f6288dd2 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -1,22 +1,24 @@ +from collections import deque from pathlib import Path from typing import Optional -# from typing import Union - import numpy as np import pandas as pd from tlo import DateOffset, Module, Parameter, Population, Property, Simulation, Types, logging -from collections import deque from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor from tlo.methods import Metadata +from tlo.methods.dxmanager import DxTest from tlo.methods.hsi_event import HSI_Event from tlo.methods.hsi_generic_first_appts import HSIEventScheduler # from tlo.methods.symptommanager import Symptom from tlo.population import IndividualProperties from tlo.util import read_csv_files +# from typing import Union + + logger = logging.getLogger(__name__) logger.setLevel(logging.INFO) @@ -25,7 +27,12 @@ class CMDChronicKidneyDisease(Module): """This is the Chronic Kidney Disease module for kidney transplants.""" INIT_DEPENDENCIES = {'SymptomManager', 'Lifestyle', 'HealthSystem', 'CardioMetabolicDisorders'} - ADDITIONAL_DEPENDENCIES = set() + + OPTIONAL_INIT_DEPENDENCIES = {'HealthBurden', 'Hiv', 'Tb', 'Epi'} + + ADDITIONAL_DEPENDENCIES = {'Depression'} + + # ADDITIONAL_DEPENDENCIES = set() METADATA = { Metadata.DISEASE_MODULE, @@ -74,6 +81,26 @@ class CMDChronicKidneyDisease(Module): Types.INT, "Maximum number of kidney transplant surgeries that can be performed in a month" ), + "prob_staging_referral": Parameter( + Types.REAL, + "Proportion of eligible population that gets referred for CKD staging" + ), + "prob_stage5": Parameter( + Types.REAL, + "Proportion of eligible population referred for CKD staging that gets CKD stage 5" + ), + "sensitivity_of_ckd_staging_test": Parameter( + Types.REAL, "sensitivity of psa staging test for CKD" + ), + "sensitivity_of_renal_clinic_test": Parameter( + Types.REAL, "sensitivity of renal clinic test for CKD" + ), + "sensitivity_of_kidney_transplant_eval_tests": Parameter( + Types.REAL, "sensitivity of kidney transplant evaluation tests for CKD" + ), + "sensitivity_of_kidney_transplant_surgery_tests": Parameter( + Types.REAL, "sensitivity of kidney transplant surgery tests for CKD" + ), } PROPERTIES = { @@ -107,9 +134,13 @@ class CMDChronicKidneyDisease(Module): Types.BOOL, "Whether this person uses herbal medicine" ), "nc_ckd_total_dialysis_sessions": Property(Types.INT, - "total number of dialysis sessions the person has ever had"), + "Total number of dialysis sessions the person has ever had"), "nc_ckd_on_dialysis": Property(Types.BOOL, "Whether this person is on dialysis"), + "ckd_date_transplant": Property( + Types.DATE, + "The date of kidney transplant (pd.NaT if never transplanted)" + ), } def __init__(self): @@ -154,6 +185,7 @@ def initialise_population(self, population: Population) -> None: df.loc[list(alive_ckd_idx), "ckd_date_diagnosis"] = pd.NaT df.loc[list(alive_ckd_idx), "nc_ckd_total_dialysis_sessions"] = 0 df.loc[list(alive_ckd_idx), "nc_ckd_on_dialysis"] = False + df.loc[list(alive_ckd_idx), "ckd_date_transplant"] = pd.NaT df.loc[list(alive_ckd_idx), "uses_herbal_medicine"] = \ self.rng.random(len(alive_ckd_idx)) < p['prop_herbal_use_ckd'] @@ -168,10 +200,11 @@ def initialise_population(self, population: Population) -> None: LinearModelType.MULTIPLICATIVE, sum(p['init_prob_any_ckd']), Predictor('nc_diabetes').when(True, p['rp_ckd_nc_diabetes']), - Predictor('hv_inf').when(True, p['rp_ckd_hiv_infection']), - Predictor('li_bmi').when(True, p['rp_ckd_li_bmi']), - Predictor('nc_hypertension').when(True, p['rp_ckd_nc_hypertension']), - Predictor('nc_chronic_ischemic_hd').when(True, p['rp_ckd_nc_chronic_ischemic_hd']), + Predictor().when('hv_inf', p['rp_ckd_hiv_infection']), + # todo find parameters for all 5 categories of bmi if its a direct risk factor + # Predictor('li_bmi').when(True, p['rp_ckd_li_bmi']), + Predictor().when('nc_hypertension', p['rp_ckd_nc_hypertension']), + Predictor().when('nc_chronic_ischemic_hd', p['rp_ckd_nc_chronic_ischemic_hd']), Predictor('uses_herbal_medicine').when(True, p['rp_ckd_herbal_use_baseline']), ) @@ -211,6 +244,45 @@ def initialise_simulation(self, sim: Simulation) -> None: self.make_the_linear_models() self.look_up_consumable_item_codes() + # ----- DX TESTS ----- + # Create the diagnostic test representing ckd staging + self.sim.modules['HealthSystem'].dx_manager.register_dx_test( + ckd_staging_test=DxTest( + property='ckd_status', + sensitivity=self.parameters['sensitivity_of_ckd_staging_test'], + target_categories=["stage1_4", "stage5"] + ) + ) + # Create the diagnostic test representing ckd renal clinic and medication + self.sim.modules['HealthSystem'].dx_manager.register_dx_test( + renal_clinic_test=DxTest( + property='ckd_status', + sensitivity=self.parameters['sensitivity_of_renal_clinic_test'], + # todo do we really need a test for this? People who take this are already + # in stage1_4 from HSI_CKD_Staging + target_categories=["stage1_4"] + ) + ) + + # Create the diagnostic test representing ckd kidney transplant evaluation + self.sim.modules['HealthSystem'].dx_manager.register_dx_test( + kidney_transplant_eval_tests=DxTest( + property='ckd_status', + sensitivity=self.parameters['sensitivity_of_kidney_transplant_eval_tests'], + target_categories=["stage5"] + ) + ) + + # Create the diagnostic test representing ckd kidney transplant surgery tests + self.sim.modules['HealthSystem'].dx_manager.register_dx_test( + kidney_transplant_surgery_tests=DxTest( + property='ckd_status', + sensitivity=self.parameters['sensitivity_of_kidney_transplant_surgery_tests'], + target_categories=["stage5"] + ) + ) + + # ----- DISABILITY-WEIGHTS ----- if "HealthBurden" in self.sim.modules: # For those with End-stage renal disease, with kidney transplant (any stage after stage1_4) @@ -253,6 +325,7 @@ def on_birth(self, mother_id: int, child_id: int) -> None: self.sim.population.props.at[child_id, 'ckd_date_diagnosis'] = pd.NaT self.sim.population.props.at[child_id, 'nc_ckd_total_dialysis_sessions'] = 0 self.sim.population.props.at[child_id, 'nc_ckd_on_dialysis'] = False + self.sim.population.props.at[child_id, 'ckd_date_transplant'] = pd.NaT def on_simulation_end(self) -> None: pass @@ -281,23 +354,26 @@ def look_up_consumable_item_codes(self): get_item_codes = self.sim.modules['HealthSystem'].get_item_code_from_item_name self.cons_item_codes = dict() + + self.cons_item_codes['ckd_staging_consumables'] = { + get_item_codes("Glove disposable latex medium_100_CMST"): 1, + get_item_codes("Blood collection tube"): 1, + get_item_codes("Reagents"): 1 + } self.cons_item_codes['renal_consumables'] = { - get_item_codes("Sterile syringe"): 1, - get_item_codes('Sterile drapes and supplies'): 3, - get_item_codes('Gloves, exam, latex, disposable, pair'): 4, - get_item_codes("Catheter"): 1, - get_item_codes("Disinfectant"): 1 - }, + get_item_codes("Gloves, exam, latex, disposable, pair"): 4, + get_item_codes("Catheter Foley's + urine bag (2000ml) 14g_each_CMST"): 1, + get_item_codes("Disinfectant for hands, alcohol-based, 1 litre bottle"): 1 + } self.cons_item_codes['kidney_transplant_eval_cons'] = { get_item_codes("Blood collection tube"): 1, - get_item_codes('Reagents'): 3, - get_item_codes('Radiopharmaceuticals'): 4 + get_item_codes("Reagents"): 3, + get_item_codes("Radiopharmaceuticals"): 4 } - self.cons_item_codes['kidney_transplant_surgery_cons'] = { # Prepare surgical instruments # administer an IV - get_item_codes('Cannula iv (winged with injection pot) 18_each_CMST'): 1, + get_item_codes("Cannula iv (winged with injection pot) 18_each_CMST"): 1, get_item_codes("Giving set iv administration + needle 15 drops/ml_each_CMST"): 1, get_item_codes("ringer's lactate (Hartmann's solution), 1000 ml_12_IDA"): 2000, # request a general anaesthetic @@ -316,8 +392,8 @@ def look_up_consumable_item_codes(self): get_item_codes("Ampicillin injection 500mg, PFR_each_CMST"): 2, #change this to immunosuppressive drugs # equipment used by surgeon, gloves and facemask - get_item_codes('Disposables gloves, powder free, 100 pieces per box'): 1, - get_item_codes('surgical face mask, disp., with metal nose piece_50_IDA'): 1, + get_item_codes("Disposables gloves, powder free, 100 pieces per box"): 1, + get_item_codes("surgical face mask, disp., with metal nose piece_50_IDA"): 1, # request syringe get_item_codes("Syringe, Autodisable SoloShot IX "): 1 } @@ -331,6 +407,7 @@ def __init__(self, module): def apply(self, population: Population) -> None: df = population.props + p = self.module.parameters had_treatment_during_this_stage = \ df.is_alive & ~pd.isnull(df.ckd_date_treatment) & \ @@ -353,42 +430,26 @@ def apply(self, population: Population) -> None: # ----------------------------SELECTING INDIVIDUALS FOR CKD Diagnosis by stage----------------------------# - eligible_population = ( + staging_eligible_population = ( (df.is_alive & df.nc_chronic_kidney_disease) & (df.ckd_status == 'pre_diagnosis') & - (df.age_years >= 20) & + (df.age_years >= 18) & (pd.isna(df.ckd_date_diagnosis)) ) - eligible_idx = df.loc[eligible_population].index + staging_eligible_idx = df.loc[staging_eligible_population].index - if not eligible_idx.empty: - probs = [ - self.module.parameters['prob_ckd_renal_clinic'], - self.module.parameters['prob_ckd_transplant_eval'] - ] + if not staging_eligible_idx.empty: + selected_for_staging = self.module.rng.random(len(staging_eligible_idx)) < p['prob_staging_referral'] + staging_idx = staging_eligible_idx[selected_for_staging] - hsi_choices = self.module.rng.choice( - ['renal_clinic', 'transplant_eval'], - size=len(eligible_idx), - p=probs - ) - #todo stage1_4 should go to renal_clinic and stage5 should go to transplant_eval and/or haemodialysis - for person_id, hsi_choice in zip(eligible_idx, hsi_choices): - if hsi_choice == 'renal_clinic': - self.sim.modules['HealthSystem'].schedule_hsi_event( - hsi_event=HSI_Renal_Clinic_and_Medication(self.module, person_id), - priority=1, - topen=self.sim.date, - tclose=self.sim.date + DateOffset(months=1), - ) - elif hsi_choice == 'transplant_eval': - self.sim.modules['HealthSystem'].schedule_hsi_event( - hsi_event=HSI_Kidney_Transplant_Evaluation(self.module, person_id), - priority=1, - topen=self.sim.date, - tclose=self.sim.date + DateOffset(months=1), - ) + for person_id in staging_idx: + self.sim.modules['HealthSystem'].schedule_hsi_event( + hsi_event=HSI_CKD_Staging(self.module, person_id), + priority=0, + topen=self.sim.date, + tclose=None, + ) surgeries_done = 0 @@ -422,6 +483,75 @@ def do_at_generic_first_appt( pass +class HSI_CKD_Staging(HSI_Event, IndividualScopeEventMixin): + """ + This is an event where CKD is diagnosed and the person could be in any stage from 1 to 5""" + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + assert isinstance(module, CMDChronicKidneyDisease) + + # Define the necessary information for an HSI + self.TREATMENT_ID = 'CKD_Staging' + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) + self.ACCEPTED_FACILITY_LEVEL = '2' + self.ALERT_OTHER_DISEASES = [] + + def apply(self, person_id, squeeze_factor): + logger.debug(key='debug', + data=f'This is HSI_CKD_Staging for person {person_id}') + df = self.sim.population.props + person = df.loc[person_id] + hs = self.sim.modules["HealthSystem"] + p = self.module.parameters + + if not df.at[person_id, 'is_alive']: + # The person is not alive, the event did not happen: so return a blank footprint + return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + + # if person has already been diagnosed, do nothing + if person["ckd_diagnosed"]: + return self.sim.modules["HealthSystem"].get_blank_appt_footprint() + + is_cons_available = self.get_consumables( + self.module.cons_item_codes['ckd_staging_consumables'] + ) + + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='ckd_staging_test', + hsi_event=self + ) + + if dx_result and is_cons_available: + self.add_equipment({'Weighing scale', 'Blood pressure machine', 'Red blood bottle', + 'Urine dip Stick', 'Ultrasound scanning machine'}) + + if self.module.rng.random() < p['prob_stage5']: + df.at[person_id, 'ckd_status'] = 'stage5' + else: + df.at[person_id, 'ckd_status'] = 'stage1_4' + + df.at[person_id, 'ckd_diagnosed'] = True + df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date + + if df.at[person_id, 'ckd_status'] == 'stage1_4': + # Conservative management at renal clinic + hs.schedule_hsi_event( + hsi_event=HSI_Renal_Clinic_and_Medication(self.module, person_id), + priority=1, + topen=self.sim.date, + tclose=self.sim.date + DateOffset(months=1), + ) + + elif df.at[person_id, 'ckd_status'] == 'stage5': + hs.schedule_hsi_event( + hsi_event=HSI_Kidney_Transplant_Evaluation(self.module, person_id), + priority=1, + topen=self.sim.date, + tclose=self.sim.date + DateOffset(months=1), + ) + + class HSI_Renal_Clinic_and_Medication(HSI_Event, IndividualScopeEventMixin): """ This is an event where a CKD patient is managed conservatively; attending clinic monthly for symptom management""" @@ -463,10 +593,12 @@ def apply(self, person_id, squeeze_factor): ) if dx_result and is_cons_available: - # record date of diagnosis - df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date + # record date of treatment + df.at[person_id, 'ckd_on_treatment'] = True df.at[person_id, 'ckd_date_treatment'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] + self.add_equipment({'Weighing scale', 'Blood pressure machine', 'Purple blood bottle', 'Red blood bottle' + 'Ultrasound scanning machine', 'Electrocardiogram', 'Oxygen concentrator'}) next_session = self.sim.date + pd.DateOffset(months=1) self.sim.modules['HealthSystem'].schedule_hsi_event(self, @@ -475,50 +607,6 @@ def apply(self, person_id, squeeze_factor): priority=1) -class HSI_Haemodialysis_Refill(HSI_Event, IndividualScopeEventMixin): - """This is an event in which a person goes for dialysis sessions 2 times a week - adding up to 8 times a month.""" - - def __init__(self, module, person_id): - super().__init__(module, person_id=person_id) - - self.TREATMENT_ID = 'CKD_Treatment_Haemodialysis' - self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' - - def apply(self, person_id, squeeze_factor): - df = self.sim.population.props - - if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'nc_chronic_kidney_disease']: - return self.sim.modules['HealthSystem'].get_blank_appt_footprint() - - if not df.at[person_id, 'nc_ckd_on_dialysis']: - df.at[person_id, 'nc_ckd_on_dialysis'] = True - - # Increment total number of dialysis sessions the person has ever had in their lifetime - df.at[person_id, 'nc_ckd_total_dialysis_sessions'] += 1 - - self.add_equipment({'Chair', 'Dialysis Machine', 'Dialyser (Artificial Kidney)', - 'Bloodlines', 'Dialysate solution', 'Dialysis water treatment system'}) - - next_session_date = self.sim.date + pd.DateOffset(days=3) - self.sim.modules['HealthSystem'].schedule_hsi_event(self, - topen=next_session_date, - tclose=next_session_date + pd.DateOffset(days=1), - priority=1 - ) - - def never_ran(self) -> None: - """What to do if the event is never run by the HealthSystem""" - # Reschedule this HSI to happen again 3 days time. - next_session_date = self.sim.date + pd.DateOffset(days=3) - self.sim.modules['HealthSystem'].schedule_hsi_event(self, - topen=next_session_date, - tclose=next_session_date + pd.DateOffset(days=1), - priority=1 - ) - - class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): """ This is the event a person undergoes in order to determine whether an individual is eligible for a kidney transplant @@ -544,12 +632,10 @@ def apply(self, person_id, squeeze_factor): # The person is not alive, the event did not happen: so return a blank footprint return self.sim.modules['HealthSystem'].get_blank_appt_footprint() - # if person already on treatment or not yet diagnosed, do nothing - if person["ckd_on_treatment"] or not person["ckd_diagnosed"]: + # if person not yet diagnosed, do nothing + if not person["ckd_diagnosed"]: return self.sim.modules["HealthSystem"].get_blank_appt_footprint() - assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) - is_cons_available = self.get_consumables( self.module.cons_item_codes['kidney_transplant_eval_cons'] ) @@ -560,12 +646,11 @@ def apply(self, person_id, squeeze_factor): ) if dx_result and is_cons_available: - # record date of diagnosis - df.at[person_id, 'ckd_date_diagnosis'] = self.sim.date - df.at[person_id, 'ckd_date_treatment'] = self.sim.date + # record date of treatment + df.at[person_id, 'ckd_date_treatment'] = self.sim.date #todo should be transplant eval date? df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] - # Append to waiting list if treatment is successful + # Append to waiting list if evaluation is successful self.module.kidney_transplant_waiting_list.append(person_id) else: @@ -577,6 +662,50 @@ def apply(self, person_id, squeeze_factor): ) +class HSI_Haemodialysis_Refill(HSI_Event, IndividualScopeEventMixin): + """This is an event in which a person goes for dialysis sessions 2 times a week + adding up to 8 times a month.""" + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + + self.TREATMENT_ID = 'CKD_Treatment_Haemodialysis' + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) + self.ACCEPTED_FACILITY_LEVEL = '3' + + def apply(self, person_id, squeeze_factor): + df = self.sim.population.props + + if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'nc_chronic_kidney_disease']: + return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + + if not df.at[person_id, 'nc_ckd_on_dialysis']: + df.at[person_id, 'nc_ckd_on_dialysis'] = True + + # Increment total number of dialysis sessions the person has ever had in their lifetime + df.at[person_id, 'nc_ckd_total_dialysis_sessions'] += 1 + + self.add_equipment({'Chair', 'Dialysis machine', 'Dialyser (Artificial Kidney)', + 'Bloodlines', 'Dialysate solution', 'Dialysis water treatment system'}) + + next_session_date = self.sim.date + pd.DateOffset(days=3) + self.sim.modules['HealthSystem'].schedule_hsi_event(self, + topen=next_session_date, + tclose=next_session_date + pd.DateOffset(days=1), + priority=1 + ) + + def never_ran(self) -> None: + """What to do if the event is never run by the HealthSystem""" + # Reschedule this HSI to happen again 3 days time. + next_session_date = self.sim.date + pd.DateOffset(days=3) + self.sim.modules['HealthSystem'].schedule_hsi_event(self, + topen=next_session_date, + tclose=next_session_date + pd.DateOffset(days=1), + priority=1 + ) + + class HSI_Kidney_Transplant_Surgery(HSI_Event, IndividualScopeEventMixin): """ This is the event a person undergoes in order to determine whether an individual is eligible for a kidney transplant @@ -587,8 +716,8 @@ def __init__(self, module, person_id): assert isinstance(module, CMDChronicKidneyDisease) # Define the necessary information for an HSI - # todo need to update priority number in resource files - self.TREATMENT_ID = 'CKD_Kidney_Transplant' + # todo need to update priority number in resource files (for all HSIs) + self.TREATMENT_ID = 'CKD_Kidney_Transplant_Surgery' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.ALERT_OTHER_DISEASES = [] @@ -603,12 +732,6 @@ def apply(self, person_id, squeeze_factor): # The person is not alive, the event did not happen: so return a blank footprint return self.sim.modules['HealthSystem'].get_blank_appt_footprint() - # if person already on treatment or not yet diagnosed, do nothing - if person["ckd_on_treatment"] or not person["ckd_diagnosed"]: - return self.sim.modules["HealthSystem"].get_blank_appt_footprint() - - assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) - is_cons_available = self.get_consumables( self.module.cons_item_codes['kidney_transplant_surgery_cons'] ) @@ -622,11 +745,11 @@ def apply(self, person_id, squeeze_factor): transplant_successful = self.module.rng.random() < self.module.parameters['prob_transplant_success'] if dx_result and is_cons_available: - self.add_equipment({'Patient monitors', 'Infusion pump', 'Dialysis machine', 'Bloodlines', 'Water tank', + self.add_equipment({'Patient monitor', 'Infusion pump', 'Dialysis machine', 'Bloodlines', 'Water tank', 'Reverse osmosis machine', 'Water softener', 'Carbon filter', '5 micro filter', - 'ventilator', 'Electrocautery unit', 'Suction machine', 'theatre bed', - 'cold static storage' 'perfusion machine', 'Ultrasound machine', 'drip stand', - 'trolley', }) + 'Ventilator', 'Electrocautery unit', 'Suction machine', 'Theatre bed', + 'Cold static storage' 'Perfusion machine', 'Ultrasound scanning machine', 'Drip stand', + 'Trolley, patient'}) if transplant_successful: # df.at[person_id, 'ckd_transplant_successful'] = True @@ -659,7 +782,7 @@ class HSI_AntiRejectionDrug_Refill(HSI_Event, IndividualScopeEventMixin): """This is an event where a kidney transplant recipient gets drugs every month for the rest of their lives """ def __init__(self, module, person_id): - super().__init__(module, person_id) + super().__init__(module, person_id=person_id) self.TREATMENT_ID = 'CKD_AntiRejectionDrug_Refill' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '2' diff --git a/tests/test_cmd_chronic_kidney_disease.py b/tests/test_cmd_chronic_kidney_disease.py index 146017135e..e3d169ccfa 100644 --- a/tests/test_cmd_chronic_kidney_disease.py +++ b/tests/test_cmd_chronic_kidney_disease.py @@ -11,11 +11,14 @@ demography, depression, enhanced_lifestyle, + epi, healthburden, healthseekingbehaviour, healthsystem, + hiv, simplified_births, symptommanager, + tb, ) try: @@ -49,6 +52,9 @@ def get_simulation(seed): cardio_metabolic_disorders.CardioMetabolicDisorders(), cmd_chronic_kidney_disease.CMDChronicKidneyDisease(), depression.Depression(), + hiv.Hiv(), + epi.Epi(), + tb.Tb(), ) return sim @@ -75,6 +81,9 @@ def get_simulation_healthsystemdisabled(seed): cardio_metabolic_disorders.CardioMetabolicDisorders(), cmd_chronic_kidney_disease.CMDChronicKidneyDisease(), depression.Depression(), + hiv.Hiv(), + epi.Epi(), + tb.Tb(), ) return sim @@ -95,7 +104,10 @@ def get_simulation_nohsi(seed): healthburden.HealthBurden(), cardio_metabolic_disorders.CardioMetabolicDisorders(), cmd_chronic_kidney_disease.CMDChronicKidneyDisease(), - depression.Depression() + depression.Depression(), + hiv.Hiv(), + epi.Epi(), + tb.Tb(), ) return sim From 6657544d2b99316652dfd306d7d7f1edfa7fb0cb Mon Sep 17 00:00:00 2001 From: thewati Date: Tue, 9 Dec 2025 15:59:56 +0200 Subject: [PATCH 23/31] . --- .../clinics/ResourceFile_ClinicConfigurations/Default.csv | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/healthsystem/human_resources/clinics/ResourceFile_ClinicConfigurations/Default.csv b/resources/healthsystem/human_resources/clinics/ResourceFile_ClinicConfigurations/Default.csv index 88c9a3cb73..871f162935 100644 --- a/resources/healthsystem/human_resources/clinics/ResourceFile_ClinicConfigurations/Default.csv +++ b/resources/healthsystem/human_resources/clinics/ResourceFile_ClinicConfigurations/Default.csv @@ -1 +1,3 @@ -Facility_ID,Officer_Type_Code,GenericClinic +version https://git-lfs.github.com/spec/v1 +oid sha256:cd312903ff50d5233d81075b1f38e7879b8933e3ad7067d52c696e4f37e51eac +size 44 From dc9f283aaaaa2b6f75e1c9365d02bc688002c212 Mon Sep 17 00:00:00 2001 From: thewati Date: Tue, 9 Dec 2025 16:01:37 +0200 Subject: [PATCH 24/31] . --- .../clinics/ResourceFile_ClinicConfigurations/Default.csv | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/resources/healthsystem/human_resources/clinics/ResourceFile_ClinicConfigurations/Default.csv b/resources/healthsystem/human_resources/clinics/ResourceFile_ClinicConfigurations/Default.csv index 88c9a3cb73..871f162935 100644 --- a/resources/healthsystem/human_resources/clinics/ResourceFile_ClinicConfigurations/Default.csv +++ b/resources/healthsystem/human_resources/clinics/ResourceFile_ClinicConfigurations/Default.csv @@ -1 +1,3 @@ -Facility_ID,Officer_Type_Code,GenericClinic +version https://git-lfs.github.com/spec/v1 +oid sha256:cd312903ff50d5233d81075b1f38e7879b8933e3ad7067d52c696e4f37e51eac +size 44 From dd6da41de98537256473fcbc8f112b2ed6fc983a Mon Sep 17 00:00:00 2001 From: thewati Date: Wed, 10 Dec 2025 16:56:23 +0200 Subject: [PATCH 25/31] death event for those on dialysis --- .../parameter_values.csv | 4 +- .../CVD.csv | 4 +- .../ClinicallyVulnerable.csv | 4 +- .../Default.csv | 4 +- .../EHP_III.csv | 4 +- .../LCOA_EHP.csv | 4 +- .../Naive.csv | 4 +- .../RMNCH.csv | 4 +- .../Test Mode 1.csv | 4 +- .../Test.csv | 4 +- .../VerticalProgrammes.csv | 4 +- src/tlo/methods/cmd_chronic_kidney_disease.py | 229 ++++++++++++++---- 12 files changed, 204 insertions(+), 69 deletions(-) diff --git a/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv b/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv index 22315e32df..e3ff4a8970 100644 --- a/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv +++ b/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c91712951c3d75e2e76ceaeabc7f9e6d9fcd15909382b965aa9a289db5b04b0 -size 1005 +oid sha256:f3c97879d08b291c697858ce98a0a5c43955dac8e8e1c1bd4e38a0be62822689 +size 1152 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv index 66c18895a6..954b37bc83 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/CVD.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c8f366c06baafd5a468937a3fd095d811b6a871d6145a491cecaeb2262aaf074 -size 4414 +oid sha256:f2b6f15ff8955fa335bf776e58b0ec87c791f743934a55bb074ac7d573f4013f +size 4610 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv index 8c7387593e..ba6599525c 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/ClinicallyVulnerable.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eabdf766eee9b232eb05abd75c2ed01443e17277b87c01d5875f53a253032b36 -size 4014 +oid sha256:a27ce2c6449f8c380120420d7b7495bcd2373b70f94598c238b3515359c335d2 +size 4210 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv index 10cc095a7f..0029074c68 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Default.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d3a76b2679e379622ba55af6d61cf16275f3e34d611a63f383afdc2b8afa7fc3 -size 4416 +oid sha256:e53561384d30ee67291fce4b2ebafbb5787f7141d444616cd8afdb92b5d063bd +size 4612 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv index 598bec4f33..c87e7a7ee1 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/EHP_III.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:786612923e290b5902bbefec6e50649dc23f38ed79c730e93c11ed55d9bc7378 -size 4415 +oid sha256:ecd9d4941bf0c9c00942bb2d82f7fb0bf1288011d871d8c4bf1e85923bf3cb9a +size 4611 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv index 88392eb6d3..f15be90fda 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/LCOA_EHP.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f98ca9d117b79d9223c3317759f3097b5d663f3b855187227e0ac3f996a11b6 -size 4414 +oid sha256:28dcfe9ffea6cbcb42cb95dfdc9799bae27ffbc76cb33958816e469fe81b8c0b +size 4610 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv index 99a027e17d..1c91ecc86c 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Naive.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:183363c820a1b9f7e328cf140a2bcfb4c299f8c11ff21d9ad7ba7cc97a4d7c0b -size 4414 +oid sha256:71a3d98170aaade88a8e4ffc68b9dc9baf3eefb71d71b4d4bb318ddbb6c65c76 +size 4610 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv index 9a67dab90f..b7b05d74c4 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/RMNCH.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b5547bf822420b4204653c1a86163c951e3163d7f0a7e3d433f9188d9d3ce5ac -size 4394 +oid sha256:6703d85cec6f248465c35d7a78f53e7cc96277a64820af43d97c36e3571fdfea +size 4590 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv index 58b158a324..fb5c4c0ec7 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test Mode 1.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:91f40e21a24b2e4e4437d9122df4b310a7610100306750e99b11fe77f51fe62e -size 4416 +oid sha256:d6e29f1e2545c274e263773502dc641ea6a20d77e626b471996d477adf666786 +size 4612 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv index 5c0a4ec545..2d698d6c5f 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/Test.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2d9a8578278a005e9b961938309f0b5d2defebeda22f975d2f2485cbe65d041b -size 4416 +oid sha256:b96bc3f9f332b27b0866302fcfb41ea859de7a1f8f5c14061ac9aa3d920e3897 +size 4612 diff --git a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv index 8a2ad0583c..bbb006d651 100644 --- a/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv +++ b/resources/healthsystem/priority_policies/ResourceFile_PriorityRanking_ALLPOLICIES/VerticalProgrammes.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5254cc538724a1bc496222f66625cf93994f18b8f2d968fde071dc467ece914c -size 4414 +oid sha256:4f7fb79ae75420c985f4ab6d8e57bae462947c9302589c53f5d924a707e7403a +size 4610 diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index b3f6288dd2..7c059fa2b3 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -6,9 +6,9 @@ import pandas as pd from tlo import DateOffset, Module, Parameter, Population, Property, Simulation, Types, logging -from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent +from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent, Event from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata +from tlo.methods import Metadata, cardio_metabolic_disorders from tlo.methods.dxmanager import DxTest from tlo.methods.hsi_event import HSI_Event from tlo.methods.hsi_generic_first_appts import HSIEventScheduler @@ -101,6 +101,19 @@ class CMDChronicKidneyDisease(Module): "sensitivity_of_kidney_transplant_surgery_tests": Parameter( Types.REAL, "sensitivity of kidney transplant surgery tests for CKD" ), + "prob_dialysis_death_1_year": Parameter( + Types.REAL, + "Probability of death for those who have been on dialysis for at least 1 year" + ), + "prob_dialysis_death_5_years": Parameter( + Types.REAL, + "Probability of death for those who have been on dialysis for at least 5 year" + ), + "prob_dialysis_death_10_years": Parameter( + Types.REAL, + "Probability of death for those who have been on dialysis for at least 10 year" + ), + } PROPERTIES = { @@ -141,6 +154,9 @@ class CMDChronicKidneyDisease(Module): Types.DATE, "The date of kidney transplant (pd.NaT if never transplanted)" ), + "ckd_death_event_scheduled": Property(Types.BOOL, + "Whether death event has been been scheduled for person"), + } def __init__(self): @@ -186,6 +202,8 @@ def initialise_population(self, population: Population) -> None: df.loc[list(alive_ckd_idx), "nc_ckd_total_dialysis_sessions"] = 0 df.loc[list(alive_ckd_idx), "nc_ckd_on_dialysis"] = False df.loc[list(alive_ckd_idx), "ckd_date_transplant"] = pd.NaT + df.loc[list(alive_ckd_idx), "ckd_death_event_scheduled"] = False + df.loc[list(alive_ckd_idx), "uses_herbal_medicine"] = \ self.rng.random(len(alive_ckd_idx)) < p['prop_herbal_use_ckd'] @@ -326,6 +344,7 @@ def on_birth(self, mother_id: int, child_id: int) -> None: self.sim.population.props.at[child_id, 'nc_ckd_total_dialysis_sessions'] = 0 self.sim.population.props.at[child_id, 'nc_ckd_on_dialysis'] = False self.sim.population.props.at[child_id, 'ckd_date_transplant'] = pd.NaT + self.sim.population.props.at[child_id, 'ckd_death_event_scheduled'] = False def on_simulation_end(self) -> None: pass @@ -398,6 +417,29 @@ def look_up_consumable_item_codes(self): get_item_codes("Syringe, Autodisable SoloShot IX "): 1 } + def schedule_dialysis_death_event(self, person_id): + """Start periodic dialysis mortality checking for a person.""" + df = self.sim.population.props + + if not df.at[person_id, 'is_alive']: + return + + if not df.at[person_id, 'nc_ckd_on_dialysis']: + return + + # Just to prevent double-scheduling + if df.at[person_id, 'ckd_death_event_scheduled']: + return + + # Schedule first death check 6 months from now + first_check = self.sim.date + DateOffset(months=6) + self.sim.schedule_event( + CKD_DialysisDeathEvent(self, person_id), + first_check + ) + + df.at[person_id, 'ckd_death_event_scheduled'] = True + class CMDChronicKidneyDiseasePollEvent(RegularEvent, PopulationScopeEventMixin): """An event that controls the development process of Chronic Kidney Disease (CKD) and logs current states.""" @@ -428,13 +470,34 @@ def apply(self, population: Population) -> None: stage1_to_4_to_stage5_idx = df.index[np.where(stage1_to_4_to_stage5)[0]] df.loc[stage1_to_4_to_stage5_idx, 'ckd_status'] = 'stage5' - # ----------------------------SELECTING INDIVIDUALS FOR CKD Diagnosis by stage----------------------------# + # ------------SELECTING INDIVIDUALS already on dialysis from cardio_metabolic_disorders module-----------# + people_on_dialysis_from_cmd = ( + df.is_alive & + df.nc_chronic_kidney_disease & + (df.nc_ckd_total_dialysis_sessions > 0) & + (df.ckd_status.isin(['pre_diagnosis', 'stage1_4'])) + ) + + # Set these people as on dialysis and on stage5 + dialysis_idx = df.loc[people_on_dialysis_from_cmd].index + if not dialysis_idx.empty: + df.loc[dialysis_idx, 'nc_ckd_on_dialysis'] = True + df.loc[dialysis_idx, 'ckd_status'] = 'stage5' + df.loc[dialysis_idx, 'ckd_diagnosed'] = True + df.loc[dialysis_idx, 'ckd_date_diagnosis'] = self.sim.date + + # Schedule death events for those who are newly identified as on dialysis + for person_id in dialysis_idx: + if not df.at[person_id, 'ckd_death_event_scheduled']: + self.module.schedule_dialysis_death_event(person_id) + # ----------------------------SELECTING INDIVIDUALS FOR CKD Diagnosis staging----------------------------# staging_eligible_population = ( (df.is_alive & df.nc_chronic_kidney_disease) & (df.ckd_status == 'pre_diagnosis') & (df.age_years >= 18) & - (pd.isna(df.ckd_date_diagnosis)) + (pd.isna(df.ckd_date_diagnosis)) & + (~df.nc_ckd_on_dialysis) ) staging_eligible_idx = df.loc[staging_eligible_population].index @@ -454,7 +517,7 @@ def apply(self, population: Population) -> None: surgeries_done = 0 while ( - surgeries_done < self.module.parameters['max_surgeries_per_month'] + surgeries_done < p['max_surgeries_per_month'] and len(self.module.kidney_transplant_waiting_list) > 0 ): next_person = self.module.kidney_transplant_waiting_list.popleft() @@ -483,6 +546,72 @@ def do_at_generic_first_appt( pass +class CKD_DialysisDeathEvent(Event, IndividualScopeEventMixin): + """ + Performs death for individuals who have been on dialysis for certain durations. + Uses dialysis session count to determine time on dialysis. + """ + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + + def apply(self, person_id): + df = self.sim.population.props + p = self.module.parameters + + if not df.at[person_id, "is_alive"]: + return + + # Check if person is still on dialysis + if not df.at[person_id, "nc_ckd_on_dialysis"]: + return + + # Calculate approximate years on dialysis based on session count + # Assuming 3 sessions per week = 144 sessions per year + total_sessions = df.at[person_id, "nc_ckd_total_dialysis_sessions"] + approx_years_on_dialysis = total_sessions / 144.0 + + # Determine if death should occur based on the year thresholds + should_die = False + + if approx_years_on_dialysis >= 10: + # Person has been on dialysis for at least 10 years + rand_val = self.module.rng.random() + # Person dies if random value is less than probability + should_die = rand_val < p['prob_dialysis_death_10_years'] + + elif approx_years_on_dialysis >= 5: + # Person has been on dialysis for at least 5 years + rand_val = self.module.rng.random() + should_die = rand_val < p['prob_dialysis_death_5_years'] + + elif approx_years_on_dialysis >= 1: + # Person has been on dialysis for at least 1 year + rand_val = self.module.rng.random() + should_die = rand_val < p['prob_dialysis_death_1_year'] + + if should_die: + logger.debug(key="CKD_DialysisDeathEvent", + data=f"CKD_DialysisDeathEvent: scheduling death for person {person_id} " + f"on dialysis for {approx_years_on_dialysis:.1f} years on {self.sim.date}") + + self.sim.modules['Demography'].do_death( + individual_id=person_id, + cause="chronic_kidney_disease_death", + originating_module=self.module + ) + + # Reset the flag since person is now dead + df.at[person_id, 'ckd_death_event_scheduled'] = False + else: + # If person survived this check, reschedule for another check in 6 months + next_check_date = self.sim.date + DateOffset(months=6) + self.sim.schedule_event( + CKD_DialysisDeathEvent(self.module, person_id), + next_check_date + ) + + class HSI_CKD_Staging(HSI_Event, IndividualScopeEventMixin): """ This is an event where CKD is diagnosed and the person could be in any stage from 1 to 5""" @@ -492,7 +621,7 @@ def __init__(self, module, person_id): assert isinstance(module, CMDChronicKidneyDisease) # Define the necessary information for an HSI - self.TREATMENT_ID = 'CKD_Staging' + self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Staging' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '2' self.ALERT_OTHER_DISEASES = [] @@ -561,7 +690,7 @@ def __init__(self, module, person_id): assert isinstance(module, CMDChronicKidneyDisease) # Define the necessary information for an HSI - self.TREATMENT_ID = 'CKD_Renal_Medication' + self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Renal_Medication' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' #todo Facility Level? self.ALERT_OTHER_DISEASES = [] @@ -617,7 +746,7 @@ def __init__(self, module, person_id): assert isinstance(module, CMDChronicKidneyDisease) # Define the necessary information for an HSI - self.TREATMENT_ID = 'CKD_Kidney_Transplant_Evaluation' + self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Kidney_Transplant_Evaluation' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.ALERT_OTHER_DISEASES = [] @@ -654,46 +783,52 @@ def apply(self, person_id, squeeze_factor): self.module.kidney_transplant_waiting_list.append(person_id) else: - hs.schedule_hsi_event( - HSI_Haemodialysis_Refill(self.module, person_id), + self.sim.modules["HealthSystem"].schedule_hsi_event( + hsi_event=cardio_metabolic_disorders.HSI_CardioMetabolicDisorders_Dialysis_Refill( + person_id=person_id, + module=self.sim.modules["CardioMetabolicDisorders"], + ), + priority=1, topen=self.sim.date, - tclose=self.sim.date + pd.DateOffset(days=1), - priority=1 + tclose=self.sim.date + pd.DateOffset(days=1) ) - - -class HSI_Haemodialysis_Refill(HSI_Event, IndividualScopeEventMixin): - """This is an event in which a person goes for dialysis sessions 2 times a week - adding up to 8 times a month.""" - - def __init__(self, module, person_id): - super().__init__(module, person_id=person_id) - - self.TREATMENT_ID = 'CKD_Treatment_Haemodialysis' - self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' - - def apply(self, person_id, squeeze_factor): - df = self.sim.population.props - - if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'nc_chronic_kidney_disease']: - return self.sim.modules['HealthSystem'].get_blank_appt_footprint() - - if not df.at[person_id, 'nc_ckd_on_dialysis']: + # Set person to be on dialysis df.at[person_id, 'nc_ckd_on_dialysis'] = True - - # Increment total number of dialysis sessions the person has ever had in their lifetime - df.at[person_id, 'nc_ckd_total_dialysis_sessions'] += 1 - - self.add_equipment({'Chair', 'Dialysis machine', 'Dialyser (Artificial Kidney)', - 'Bloodlines', 'Dialysate solution', 'Dialysis water treatment system'}) - - next_session_date = self.sim.date + pd.DateOffset(days=3) - self.sim.modules['HealthSystem'].schedule_hsi_event(self, - topen=next_session_date, - tclose=next_session_date + pd.DateOffset(days=1), - priority=1 - ) + self.schedule_dialysis_death_event(person_id) + + +# class HSI_Haemodialysis_Refill(HSI_Event, IndividualScopeEventMixin): +# """This is an event in which a person goes for dialysis sessions 2 times a week +# adding up to 8 times a month.""" +# +# def __init__(self, module, person_id): +# super().__init__(module, person_id=person_id) +# +# self.TREATMENT_ID = 'CKD_Treatment_Haemodialysis' +# self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) +# self.ACCEPTED_FACILITY_LEVEL = '3' +# +# def apply(self, person_id, squeeze_factor): +# df = self.sim.population.props +# +# if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'nc_chronic_kidney_disease']: +# return self.sim.modules['HealthSystem'].get_blank_appt_footprint() +# +# if not df.at[person_id, 'nc_ckd_on_dialysis']: +# df.at[person_id, 'nc_ckd_on_dialysis'] = True +# +# # Increment total number of dialysis sessions the person has ever had in their lifetime +# df.at[person_id, 'nc_ckd_total_dialysis_sessions'] += 1 +# +# self.add_equipment({'Chair', 'Dialysis machine', 'Dialyser (Artificial Kidney)', +# 'Bloodlines', 'Dialysate solution', 'Dialysis water treatment system'}) +# +# next_session_date = self.sim.date + pd.DateOffset(days=3) +# self.sim.modules['HealthSystem'].schedule_hsi_event(self, +# topen=next_session_date, +# tclose=next_session_date + pd.DateOffset(days=1), +# priority=1 +# ) def never_ran(self) -> None: """What to do if the event is never run by the HealthSystem""" @@ -717,7 +852,7 @@ def __init__(self, module, person_id): # Define the necessary information for an HSI # todo need to update priority number in resource files (for all HSIs) - self.TREATMENT_ID = 'CKD_Kidney_Transplant_Surgery' + self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_Kidney_Transplant_Surgery' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1, 'NewAdult': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.ALERT_OTHER_DISEASES = [] @@ -783,7 +918,7 @@ class HSI_AntiRejectionDrug_Refill(HSI_Event, IndividualScopeEventMixin): def __init__(self, module, person_id): super().__init__(module, person_id=person_id) - self.TREATMENT_ID = 'CKD_AntiRejectionDrug_Refill' + self.TREATMENT_ID = 'CardioMetabolicDisorders_CKD_AntiRejectionDrug_Refill' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '2' From eb903e2dcf65d2951e122bebdcd4a00bee034e02 Mon Sep 17 00:00:00 2001 From: thewati Date: Wed, 10 Dec 2025 17:03:21 +0200 Subject: [PATCH 26/31] isort --- src/tlo/methods/cmd_chronic_kidney_disease.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 7c059fa2b3..e1467f6b77 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -6,7 +6,7 @@ import pandas as pd from tlo import DateOffset, Module, Parameter, Population, Property, Simulation, Types, logging -from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent, Event +from tlo.events import Event, IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor from tlo.methods import Metadata, cardio_metabolic_disorders from tlo.methods.dxmanager import DxTest From 1c5012a7ca2291b08eae19e65cf486f2e5ded884 Mon Sep 17 00:00:00 2001 From: thewati Date: Wed, 24 Dec 2025 22:42:23 +0200 Subject: [PATCH 27/31] analysis compare num od deaths --- .../cmd_chronic_kidney_disease_analyses.py | 106 ++++++++++++++++++ src/tlo/methods/cmd_chronic_kidney_disease.py | 22 ++-- 2 files changed, 120 insertions(+), 8 deletions(-) create mode 100644 src/scripts/cmd_chronic_kidney_disease_analyses/cmd_chronic_kidney_disease_analyses.py diff --git a/src/scripts/cmd_chronic_kidney_disease_analyses/cmd_chronic_kidney_disease_analyses.py b/src/scripts/cmd_chronic_kidney_disease_analyses/cmd_chronic_kidney_disease_analyses.py new file mode 100644 index 0000000000..8372b7e475 --- /dev/null +++ b/src/scripts/cmd_chronic_kidney_disease_analyses/cmd_chronic_kidney_disease_analyses.py @@ -0,0 +1,106 @@ +""" +Runs the CMD Chronic Kidney Disease module and produces the standard `compare_deaths` analysis to check +the number of deaths modelled against the GBD data. +""" + +import matplotlib.pyplot as plt +import pandas as pd + +from tlo import Date, Simulation, logging +from tlo.analysis.utils import compare_number_of_deaths, get_root_path +from tlo.methods import ( + cardio_metabolic_disorders, + cmd_chronic_kidney_disease, + demography, + depression, + enhanced_lifestyle, + epi, + healthburden, + healthseekingbehaviour, + healthsystem, + hiv, + simplified_births, + symptommanager, + tb, +) + +# The resource files +root = get_root_path() +resourcefilepath = root / "resources" + +log_config = { + "filename": "cmd_chronic_kidney_disease_analysis", + "directory": root / "outputs", + "custom_levels": { + "*": logging.WARNING, + "tlo.methods.demography": logging.INFO, + "tlo.methods.healthburden": logging.INFO, + } +} + +# Set parameters for the simulation +start_date = Date(2010, 1, 1) +end_date = Date(2015, 1, 1) +popsize = 1_000 + +def run_sim(): + # Establish the simulation object and set the seed + sim = Simulation(start_date=start_date, log_config=log_config, resourcefilepath=resourcefilepath) + + # Register the appropriate modules + sim.register(demography.Demography(), + simplified_births.SimplifiedBirths(), + enhanced_lifestyle.Lifestyle(), + healthsystem.HealthSystem(cons_availability='all'), + symptommanager.SymptomManager(), + healthseekingbehaviour.HealthSeekingBehaviour(), + healthburden.HealthBurden(), + cardio_metabolic_disorders.CardioMetabolicDisorders(), + cmd_chronic_kidney_disease.CMDChronicKidneyDisease(), + depression.Depression(), + hiv.Hiv(), + epi.Epi(), + tb.Tb(), + ) + + sim.make_initial_population(n=popsize) + sim.simulate(end_date=end_date) + + return sim.log_filepath + +# Create df from simulation +logfile = run_sim() + +CAUSE_NAME = 'Kidney Disease' + +# With health system +comparison = compare_number_of_deaths( + logfile=logfile, + resourcefilepath=resourcefilepath +).rename(columns={"model": "model_with_healthsystem"}) + +# Without health system +no_hs = compare_number_of_deaths( + logfile=logfile, + resourcefilepath=resourcefilepath +)["model"] +no_hs.name = "model_no_healthsystem" + +comparison = pd.concat([comparison, no_hs], axis=1) + +comparison = comparison.loc[ + ("2010-2014", slice(None), slice(None), CAUSE_NAME) +].fillna(0.0) + + +fig, axs = plt.subplots(nrows=2, sharex=True, figsize=(8, 6)) + +for ax, sex in zip(axs, ("M", "F")): + comparison.loc[sex].plot(ax=ax) + ax.set_ylabel("Deaths per year") + ax.set_title(f"Sex: {sex}") + +axs[-1].set_xlabel("Age group") + +plt.tight_layout() +plt.show() diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index e1467f6b77..29d8e08bb7 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -9,6 +9,7 @@ from tlo.events import Event, IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor from tlo.methods import Metadata, cardio_metabolic_disorders +from tlo.methods.causes import Cause from tlo.methods.dxmanager import DxTest from tlo.methods.hsi_event import HSI_Event from tlo.methods.hsi_generic_first_appts import HSIEventScheduler @@ -41,6 +42,16 @@ class CMDChronicKidneyDisease(Module): Metadata.USES_HEALTHBURDEN, } + # Declare Causes of Death + CAUSES_OF_DEATH = { + 'chronic_kidney_disease': Cause(gbd_causes='Chronic kidney disease', label='Kidney Disease'), + } + + # Declare Causes of Disability + CAUSES_OF_DISABILITY = { + 'chronic_kidney_disease': Cause(gbd_causes='Chronic kidney disease', label='Kidney Disease'), + } + PARAMETERS = { "rate_onset_to_stage1_4": Parameter(Types.REAL, "Probability of people who get diagnosed with CKD stage 1-4"), @@ -597,7 +608,7 @@ def apply(self, person_id): self.sim.modules['Demography'].do_death( individual_id=person_id, - cause="chronic_kidney_disease_death", + cause="chronic_kidney_disease", originating_module=self.module ) @@ -794,7 +805,6 @@ def apply(self, person_id, squeeze_factor): ) # Set person to be on dialysis df.at[person_id, 'nc_ckd_on_dialysis'] = True - self.schedule_dialysis_death_event(person_id) # class HSI_Haemodialysis_Refill(HSI_Event, IndividualScopeEventMixin): @@ -905,12 +915,8 @@ def apply(self, person_id, squeeze_factor): else: df.at[person_id, 'ckd_date_transplant'] = self.sim.date #todo Upile look at this logic, related to DALYs - - self.sim.modules['Demography'].do_death( - individual_id=person_id, - cause='chronic_kidney_disease_death', - originating_module=self.module - ) + # Schedule deaths + self.schedule_dialysis_death_event(person_id) class HSI_AntiRejectionDrug_Refill(HSI_Event, IndividualScopeEventMixin): From 6968ab029463e08d6216ef5288dde8f48321a956 Mon Sep 17 00:00:00 2001 From: Precious29-web Date: Wed, 7 Jan 2026 15:43:43 +0200 Subject: [PATCH 28/31] scheduled graft death --- src/tlo/methods/cmd_chronic_kidney_disease.py | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 29d8e08bb7..8f271c6073 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -124,7 +124,10 @@ class CMDChronicKidneyDisease(Module): Types.REAL, "Probability of death for those who have been on dialysis for at least 10 year" ), - + "prob_transplant_death_by_graft_failure": Parameter( + Types.REAL, + "Probability of death immediately after a kidney transplant" + ), } PROPERTIES = { @@ -873,6 +876,8 @@ def apply(self, person_id, squeeze_factor): df = self.sim.population.props person = df.loc[person_id] hs = self.sim.modules["HealthSystem"] + p = self.module.parameters + if not df.at[person_id, 'is_alive']: # The person is not alive, the event did not happen: so return a blank footprint return self.sim.modules['HealthSystem'].get_blank_appt_footprint() @@ -916,7 +921,25 @@ def apply(self, person_id, squeeze_factor): else: df.at[person_id, 'ckd_date_transplant'] = self.sim.date #todo Upile look at this logic, related to DALYs # Schedule deaths - self.schedule_dialysis_death_event(person_id) + #self.schedule_dialysis_death_event(person_id) + should_die_of_graft_failure = False + rand_val = self.module.rng.random() + should_die_of_graft_failure = rand_val < p['prob_transplant_death_by_graft_failure'] + if should_die_of_graft_failure: + self.sim.modules['Demography'].do_death( + individual_id=person_id, + cause="chronic_kidney_disease", + originating_module=self.module + ) + + else: + next_scheduled_HSI = cardio_metabolic_disorders.HSI_CardioMetabolicDisorders_Dialysis_Refill \ + if self.module.rng.random_sample() < 0.5 else HSI_Kidney_Transplant_Evaluation + hs.schedule_hsi_event( + hsi_event=next_scheduled_HSI(module=self.module, person_id=person_id), + topen=self.sim.date, + priority=0 + ) class HSI_AntiRejectionDrug_Refill(HSI_Event, IndividualScopeEventMixin): From eb26d5ce7d7127daa90bda2c358ed91285dedcd9 Mon Sep 17 00:00:00 2001 From: thewati Date: Mon, 12 Jan 2026 15:12:28 +0200 Subject: [PATCH 29/31] Plot DALYs --- .../cmd_chronic_kidney_disease_analyses.py | 113 +++++++++++++----- 1 file changed, 83 insertions(+), 30 deletions(-) diff --git a/src/scripts/cmd_chronic_kidney_disease_analyses/cmd_chronic_kidney_disease_analyses.py b/src/scripts/cmd_chronic_kidney_disease_analyses/cmd_chronic_kidney_disease_analyses.py index 8372b7e475..fb925b00ea 100644 --- a/src/scripts/cmd_chronic_kidney_disease_analyses/cmd_chronic_kidney_disease_analyses.py +++ b/src/scripts/cmd_chronic_kidney_disease_analyses/cmd_chronic_kidney_disease_analyses.py @@ -7,7 +7,7 @@ import pandas as pd from tlo import Date, Simulation, logging -from tlo.analysis.utils import compare_number_of_deaths, get_root_path +from tlo.analysis.utils import compare_number_of_deaths, get_root_path, parse_log_file, make_age_grp_types from tlo.methods import ( cardio_metabolic_disorders, cmd_chronic_kidney_disease, @@ -41,57 +41,95 @@ # Set parameters for the simulation start_date = Date(2010, 1, 1) end_date = Date(2015, 1, 1) -popsize = 1_000 +popsize = 5_000 -def run_sim(): - # Establish the simulation object and set the seed + +def get_ckd_dalys(logfile): + output = parse_log_file(logfile) + dalys = output['tlo.methods.healthburden']['dalys_stacked'] + # Keep only numeric DALY columns + age_range + numeric_cols = dalys.select_dtypes(include='number').columns + dalys = dalys[['age_range', *numeric_cols]] + + # Sum over time + dalys = ( + dalys + .groupby('age_range') + .sum() + .reindex(make_age_grp_types().categories) + .fillna(0.0) + ) + + return dalys + + +def run_sim(allow_hsi: bool): sim = Simulation(start_date=start_date, log_config=log_config, resourcefilepath=resourcefilepath) - # Register the appropriate modules - sim.register(demography.Demography(), - simplified_births.SimplifiedBirths(), - enhanced_lifestyle.Lifestyle(), - healthsystem.HealthSystem(cons_availability='all'), - symptommanager.SymptomManager(), - healthseekingbehaviour.HealthSeekingBehaviour(), - healthburden.HealthBurden(), - cardio_metabolic_disorders.CardioMetabolicDisorders(), - cmd_chronic_kidney_disease.CMDChronicKidneyDisease(), - depression.Depression(), - hiv.Hiv(), - epi.Epi(), - tb.Tb(), - ) + sim.register( + demography.Demography(), + simplified_births.SimplifiedBirths(), + enhanced_lifestyle.Lifestyle(), + healthsystem.HealthSystem( + disable=(allow_hsi is True), + disable_and_reject_all=(allow_hsi is False) + ), + symptommanager.SymptomManager(), + healthseekingbehaviour.HealthSeekingBehaviour(), + healthburden.HealthBurden(), + cardio_metabolic_disorders.CardioMetabolicDisorders(), + cmd_chronic_kidney_disease.CMDChronicKidneyDisease(), + depression.Depression(), + hiv.Hiv(), + epi.Epi(), + tb.Tb(), + ) sim.make_initial_population(n=popsize) sim.simulate(end_date=end_date) return sim.log_filepath -# Create df from simulation -logfile = run_sim() + +# With interventions +logfile_with_healthsystem = run_sim(allow_hsi=True) +dalys_with_hs = get_ckd_dalys(logfile_with_healthsystem) + +# Without interventions +logfile_no_healthsystem = run_sim(allow_hsi=False) +dalys_no_hs = get_ckd_dalys(logfile_no_healthsystem) CAUSE_NAME = 'Kidney Disease' -# With health system +# Extract CKD only (cause is a column) +ckd_dalys_with_hs = dalys_with_hs[CAUSE_NAME].fillna(0.0) +ckd_dalys_no_hs = dalys_no_hs[CAUSE_NAME].fillna(0.0) + +# With healthsystem comparison = compare_number_of_deaths( - logfile=logfile, + logfile=logfile_with_healthsystem, resourcefilepath=resourcefilepath -).rename(columns={"model": "model_with_healthsystem"}) +).rename(columns={'model': 'model_with_healthsystem'}) -# Without health system -no_hs = compare_number_of_deaths( - logfile=logfile, +# Without healthsystem +x = compare_number_of_deaths( + logfile=logfile_no_healthsystem, resourcefilepath=resourcefilepath -)["model"] -no_hs.name = "model_no_healthsystem" +)['model'] +x.name = 'model_no_healthsystem' -comparison = pd.concat([comparison, no_hs], axis=1) +comparison = pd.concat([comparison, x], axis=1) comparison = comparison.loc[ ("2010-2014", slice(None), slice(None), CAUSE_NAME) ].fillna(0.0) +comparison.index = comparison.index.droplevel( + [name for name in comparison.index.names if name in ('period', 'cause')] +) + +print("####################################### Result ###################") +print((comparison["model_with_healthsystem"] - comparison["model_no_healthsystem"]).abs().sum()) fig, axs = plt.subplots(nrows=2, sharex=True, figsize=(8, 6)) @@ -104,3 +142,18 @@ def run_sim(): plt.tight_layout() plt.show() + +# Plot for DALYs +fig, axs = plt.subplots(1, 2, figsize=(10, 4), sharey=True) + +ckd_dalys_with_hs.plot.bar(ax=axs[0]) +axs[0].set_title("CKD DALYs – With Health System") +axs[0].set_xlabel("Age group") +axs[0].set_ylabel("Total DALYs") + +ckd_dalys_no_hs.plot.bar(ax=axs[1]) +axs[1].set_title("CKD DALYs – No Health System") +axs[1].set_xlabel("Age group") + +plt.tight_layout() +plt.show() From f2222004afe9a3774fbab0b4dfe148fa43828a9f Mon Sep 17 00:00:00 2001 From: Precious29-web Date: Tue, 13 Jan 2026 11:21:43 +0200 Subject: [PATCH 30/31] Renal Clinic death event --- .../parameter_values.csv | 4 +- src/tlo/methods/cmd_chronic_kidney_disease.py | 213 ++++++++++++++---- 2 files changed, 169 insertions(+), 48 deletions(-) diff --git a/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv b/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv index e3ff4a8970..c0d1e1d3aa 100644 --- a/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv +++ b/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3c97879d08b291c697858ce98a0a5c43955dac8e8e1c1bd4e38a0be62822689 -size 1152 +oid sha256:c1e495a9d5d5d69366f008cffad15e46c90bbbf9ccae6ba4c5d758d4b0a9e6ec +size 1498 diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 8f271c6073..146e87fad0 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -128,6 +128,26 @@ class CMDChronicKidneyDisease(Module): Types.REAL, "Probability of death immediately after a kidney transplant" ), + "prob_going_for_refill_or_evaluation_from_graft_failure": Parameter( + Types.REAL, + "Probability of being referred to dialysis refill or transplant evaluation from graft failure" + ), + "prob_renal_medication_success": Parameter( + Types.REAL, + "Probability of survival in renal clinic" + ), + "prob_renal_clinic_death_1_year": Parameter( + Types.REAL, + "Probability of death for those who have been in Renal Clinic for at least 1 year" + ), + "prob_renal_clinic_death_5_years": Parameter( + Types.REAL, + "Probability of death for those who have been in Renal Clinic for at least 5 years" + ), + "prob_renal_clinic_death_10_years": Parameter( + Types.REAL, + "Probability of death for those who have been in Renal Clinic for at least 10 years" + ), } PROPERTIES = { @@ -170,6 +190,13 @@ class CMDChronicKidneyDisease(Module): ), "ckd_death_event_scheduled": Property(Types.BOOL, "Whether death event has been been scheduled for person"), + "ckd_in_renal_clinic": Property(Types.BOOL, + "Whether a person is currently attending renal clinic and medication" + ), + "ckd_date_started_renal_clinic": Property( + Types.DATE, + "The date of commencement of renal clinic" + ), } @@ -217,6 +244,8 @@ def initialise_population(self, population: Population) -> None: df.loc[list(alive_ckd_idx), "nc_ckd_on_dialysis"] = False df.loc[list(alive_ckd_idx), "ckd_date_transplant"] = pd.NaT df.loc[list(alive_ckd_idx), "ckd_death_event_scheduled"] = False + df.loc[list(alive_ckd_idx), "ckd_in_renal_clinic"] = False + df.loc[list(alive_ckd_idx), "ckd_date_started_renal_clinic"] = pd.NaT df.loc[list(alive_ckd_idx), "uses_herbal_medicine"] = \ @@ -359,6 +388,8 @@ def on_birth(self, mother_id: int, child_id: int) -> None: self.sim.population.props.at[child_id, 'nc_ckd_on_dialysis'] = False self.sim.population.props.at[child_id, 'ckd_date_transplant'] = pd.NaT self.sim.population.props.at[child_id, 'ckd_death_event_scheduled'] = False + self.sim.population.props.at[child_id, 'ckd_in_renal_clinic'] = False + self.sim.population.props.at[child_id, 'ckd_date_started_renal_clinic'] = pd.NaT def on_simulation_end(self) -> None: pass @@ -454,6 +485,28 @@ def schedule_dialysis_death_event(self, person_id): df.at[person_id, 'ckd_death_event_scheduled'] = True + def schedule_renal_clinic_death_event(self, person_id): + """Start periodic dialysis mortality checking for a person.""" + df = self.sim.population.props + + if not df.at[person_id, 'is_alive']: + return + + if not df.at[person_id, 'ckd_in_renal_clinic']: + return + + # Just to prevent double-scheduling + if df.at[person_id, 'ckd_death_event_scheduled']: + return + + # Schedule first death check 6 months from now + first_check = self.sim.date + DateOffset(months=6) + self.sim.schedule_event( + CKD_Renal_Clinic_DeathEvent(self, person_id), + first_check + ) + + df.at[person_id, 'ckd_death_event_scheduled'] = True class CMDChronicKidneyDiseasePollEvent(RegularEvent, PopulationScopeEventMixin): """An event that controls the development process of Chronic Kidney Disease (CKD) and logs current states.""" @@ -625,6 +678,71 @@ def apply(self, person_id): next_check_date ) +class CKD_Renal_Clinic_DeathEvent(Event, IndividualScopeEventMixin): + """ + Performs death for individuals who have been on dialysis for certain durations. + Uses dialysis session count to determine time on dialysis. + """ + + def __init__(self, module, person_id): + super().__init__(module, person_id=person_id) + + def apply(self, person_id): + df = self.sim.population.props + p = self.module.parameters + + if not df.at[person_id, "is_alive"]: + return + + # Check if person is still on dialysis + if not df.at[person_id, "ckd_in_renal_clinic"]: + return + + # Calculate approximate years on dialysis based on session count + # Assuming 3 sessions per week = 144 sessions per year + total_sessions = df.at[person_id, "nc_ckd_total_dialysis_sessions"] + approx_years_on_dialysis = total_sessions / 144.0 + + # Determine if death should occur based on the year thresholds + should_die = False + + if approx_years_on_dialysis >= 10: + # Person has been on dialysis for at least 10 years + rand_val = self.module.rng.random() + # Person dies if random value is less than probability + should_die = rand_val < p['prob_renal_clinic_death_10_years'] + + elif approx_years_on_dialysis >= 5: + # Person has been on dialysis for at least 5 years + rand_val = self.module.rng.random() + should_die = rand_val < p['prob_renal_clinic_death_5_years'] + + elif approx_years_on_dialysis >= 1: + # Person has been on dialysis for at least 1 year + rand_val = self.module.rng.random() + should_die = rand_val < p['prob_renal_clinic_death_1_year'] + + if should_die: + logger.debug(key="CKD_DialysisDeathEvent", + data=f"CKD_DialysisDeathEvent: scheduling death for person {person_id} " + f"on dialysis for {approx_years_on_dialysis:.1f} years on {self.sim.date}") + + self.sim.modules['Demography'].do_death( + individual_id=person_id, + cause="chronic_kidney_disease", + originating_module=self.module + ) + + # Reset the flag since person is now dead + df.at[person_id, 'ckd_death_event_scheduled'] = False + else: + # If person survived this check, reschedule for another check in 6 months + next_check_date = self.sim.date + DateOffset(months=6) + self.sim.schedule_event( + CKD_DialysisDeathEvent(self.module, person_id), + next_check_date + ) + class HSI_CKD_Staging(HSI_Event, IndividualScopeEventMixin): """ @@ -734,20 +852,23 @@ def apply(self, person_id, squeeze_factor): dx_tests_to_run='renal_clinic_test', hsi_event=self ) - + renal_medication_successful = self.module.rng.random() < self.module.parameters['prob_renal_medication_success'] if dx_result and is_cons_available: # record date of treatment - df.at[person_id, 'ckd_on_treatment'] = True + #df.at[person_id, 'ckd_on_treatment'] = True + df.at[person_id, 'ckd_in_renal_clinic'] = True df.at[person_id, 'ckd_date_treatment'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] self.add_equipment({'Weighing scale', 'Blood pressure machine', 'Purple blood bottle', 'Red blood bottle' 'Ultrasound scanning machine', 'Electrocardiogram', 'Oxygen concentrator'}) - - next_session = self.sim.date + pd.DateOffset(months=1) - self.sim.modules['HealthSystem'].schedule_hsi_event(self, - topen=next_session, - tclose=None, - priority=1) + if renal_medication_successful: + next_session = self.sim.date + pd.DateOffset(months=1) + self.sim.modules['HealthSystem'].schedule_hsi_event(self, + topen=next_session, + tclose=None, + priority=1) + else: + self.schedule_renal_clinic_death_event(person_id) class HSI_Kidney_Transplant_Evaluation(HSI_Event, IndividualScopeEventMixin): @@ -901,45 +1022,45 @@ def apply(self, person_id, squeeze_factor): 'Cold static storage' 'Perfusion machine', 'Ultrasound scanning machine', 'Drip stand', 'Trolley, patient'}) - if transplant_successful: - # df.at[person_id, 'ckd_transplant_successful'] = True - df.at[person_id, 'ckd_date_transplant'] = self.sim.date - df.at[person_id, 'nc_ckd_on_dialysis'] = False - # df.at[person_id, 'ckd_on_anti_rejection_drugs'] = True - # df.at[person_id, 'ckd_on_transplant_waiting_list'] = False - # df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] - - # Schedule monthly anti-rejection drug refill - next_month = self.sim.date + pd.DateOffset(months=1) - hs.schedule_hsi_event( - HSI_AntiRejectionDrug_Refill(self.module, person_id), - topen=next_month, - tclose=next_month + pd.DateOffset(days=5), - priority=1 - ) + if transplant_successful: + # df.at[person_id, 'ckd_transplant_successful'] = True + df.at[person_id, 'ckd_date_transplant'] = self.sim.date + df.at[person_id, 'nc_ckd_on_dialysis'] = False + # df.at[person_id, 'ckd_on_anti_rejection_drugs'] = True + # df.at[person_id, 'ckd_on_transplant_waiting_list'] = False + # df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] - else: - df.at[person_id, 'ckd_date_transplant'] = self.sim.date #todo Upile look at this logic, related to DALYs - # Schedule deaths - #self.schedule_dialysis_death_event(person_id) - should_die_of_graft_failure = False - rand_val = self.module.rng.random() - should_die_of_graft_failure = rand_val < p['prob_transplant_death_by_graft_failure'] - if should_die_of_graft_failure: - self.sim.modules['Demography'].do_death( - individual_id=person_id, - cause="chronic_kidney_disease", - originating_module=self.module - ) - - else: - next_scheduled_HSI = cardio_metabolic_disorders.HSI_CardioMetabolicDisorders_Dialysis_Refill \ - if self.module.rng.random_sample() < 0.5 else HSI_Kidney_Transplant_Evaluation - hs.schedule_hsi_event( - hsi_event=next_scheduled_HSI(module=self.module, person_id=person_id), - topen=self.sim.date, - priority=0 - ) + # Schedule monthly anti-rejection drug refill + next_month = self.sim.date + pd.DateOffset(months=1) + hs.schedule_hsi_event( + HSI_AntiRejectionDrug_Refill(self.module, person_id), + topen=next_month, + tclose=next_month + pd.DateOffset(days=5), + priority=1 + ) + + else: + df.at[person_id, 'ckd_date_transplant'] = self.sim.date #todo Upile look at this logic, related to DALYs + # Schedule deaths + #self.schedule_dialysis_death_event(person_id) + should_die_of_graft_failure = False + rand_val = self.module.rng.random() + should_die_of_graft_failure = rand_val < p['prob_transplant_death_by_graft_failure'] + if should_die_of_graft_failure: + self.sim.modules['Demography'].do_death( + individual_id=person_id, + cause="chronic_kidney_disease", + originating_module=self.module) + + else: + next_scheduled_HSI = cardio_metabolic_disorders.HSI_CardioMetabolicDisorders_Dialysis_Refill \ + if (self.module.rng.random_sample() < + p['prob_going_for_refill_or_evaluation_from_graft_failure']) else HSI_Kidney_Transplant_Evaluation + hs.schedule_hsi_event( + hsi_event=next_scheduled_HSI(module=self.module, person_id=person_id), + topen=self.sim.date, + priority=0 + ) class HSI_AntiRejectionDrug_Refill(HSI_Event, IndividualScopeEventMixin): From a6e2fd0f2c81a8c4649f30bc7a9615ef219f85f3 Mon Sep 17 00:00:00 2001 From: thewati Date: Tue, 20 Jan 2026 16:12:05 +0200 Subject: [PATCH 31/31] Remove error. Rework renal clinic death event --- .../parameter_values.csv | 4 +- src/tlo/methods/cmd_chronic_kidney_disease.py | 152 +++++++++--------- 2 files changed, 76 insertions(+), 80 deletions(-) diff --git a/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv b/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv index c0d1e1d3aa..7d58b49c49 100644 --- a/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv +++ b/resources/ResourceFile_CMD_Chronic_Kidney_Disease/parameter_values.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1e495a9d5d5d69366f008cffad15e46c90bbbf9ccae6ba4c5d758d4b0a9e6ec -size 1498 +oid sha256:043678b06b0a6fb5bbe423cca6596e3e90d5e953a06dbb819b1f31b923706e1a +size 1499 diff --git a/src/tlo/methods/cmd_chronic_kidney_disease.py b/src/tlo/methods/cmd_chronic_kidney_disease.py index 146e87fad0..45cfdee821 100644 --- a/src/tlo/methods/cmd_chronic_kidney_disease.py +++ b/src/tlo/methods/cmd_chronic_kidney_disease.py @@ -189,10 +189,10 @@ class CMDChronicKidneyDisease(Module): "The date of kidney transplant (pd.NaT if never transplanted)" ), "ckd_death_event_scheduled": Property(Types.BOOL, - "Whether death event has been been scheduled for person"), + "Whether death event has been been scheduled for person"), "ckd_in_renal_clinic": Property(Types.BOOL, - "Whether a person is currently attending renal clinic and medication" - ), + "Whether a person is currently attending renal clinic and medication" + ), "ckd_date_started_renal_clinic": Property( Types.DATE, "The date of commencement of renal clinic" @@ -247,7 +247,6 @@ def initialise_population(self, population: Population) -> None: df.loc[list(alive_ckd_idx), "ckd_in_renal_clinic"] = False df.loc[list(alive_ckd_idx), "ckd_date_started_renal_clinic"] = pd.NaT - df.loc[list(alive_ckd_idx), "uses_herbal_medicine"] = \ self.rng.random(len(alive_ckd_idx)) < p['prop_herbal_use_ckd'] df.loc[list(df.loc[df.is_alive & ~df.nc_chronic_kidney_disease].index), "uses_herbal_medicine"] = False @@ -343,7 +342,6 @@ def initialise_simulation(self, sim: Simulation) -> None: ) ) - # ----- DISABILITY-WEIGHTS ----- if "HealthBurden" in self.sim.modules: # For those with End-stage renal disease, with kidney transplant (any stage after stage1_4) @@ -508,6 +506,7 @@ def schedule_renal_clinic_death_event(self, person_id): df.at[person_id, 'ckd_death_event_scheduled'] = True + class CMDChronicKidneyDiseasePollEvent(RegularEvent, PopulationScopeEventMixin): """An event that controls the development process of Chronic Kidney Disease (CKD) and logs current states.""" @@ -668,8 +667,9 @@ def apply(self, person_id): originating_module=self.module ) - # Reset the flag since person is now dead + # Reset the flags since person is now dead df.at[person_id, 'ckd_death_event_scheduled'] = False + df.at[person_id, 'ckd_in_renal_clinic'] = False else: # If person survived this check, reschedule for another check in 6 months next_check_date = self.sim.date + DateOffset(months=6) @@ -678,10 +678,10 @@ def apply(self, person_id): next_check_date ) + class CKD_Renal_Clinic_DeathEvent(Event, IndividualScopeEventMixin): """ - Performs death for individuals who have been on dialysis for certain durations. - Uses dialysis session count to determine time on dialysis. + Performs death for individuals who have been the renal clinic for certain durations. """ def __init__(self, module, person_id): @@ -694,52 +694,51 @@ def apply(self, person_id): if not df.at[person_id, "is_alive"]: return - # Check if person is still on dialysis + # Check if person is still in renal clinic if not df.at[person_id, "ckd_in_renal_clinic"]: return - # Calculate approximate years on dialysis based on session count - # Assuming 3 sessions per week = 144 sessions per year - total_sessions = df.at[person_id, "nc_ckd_total_dialysis_sessions"] - approx_years_on_dialysis = total_sessions / 144.0 + # Calculate years in renal clinic so far + date_started = df.at[person_id, "ckd_date_started_renal_clinic"] + if pd.isnull(date_started): + return + + years_in_clinic = (self.sim.date - date_started).days / 365.25 # Determine if death should occur based on the year thresholds should_die = False - if approx_years_on_dialysis >= 10: - # Person has been on dialysis for at least 10 years + if years_in_clinic >= 10: + # Person has been in renal clinic for at least 10 years rand_val = self.module.rng.random() # Person dies if random value is less than probability should_die = rand_val < p['prob_renal_clinic_death_10_years'] - elif approx_years_on_dialysis >= 5: - # Person has been on dialysis for at least 5 years + elif years_in_clinic >= 5: + # Person has been in renal clinic for at least 5 years rand_val = self.module.rng.random() should_die = rand_val < p['prob_renal_clinic_death_5_years'] - elif approx_years_on_dialysis >= 1: - # Person has been on dialysis for at least 1 year + elif years_in_clinic >= 1: + # Person has been in renal clinic for at least 1 year rand_val = self.module.rng.random() should_die = rand_val < p['prob_renal_clinic_death_1_year'] if should_die: - logger.debug(key="CKD_DialysisDeathEvent", - data=f"CKD_DialysisDeathEvent: scheduling death for person {person_id} " - f"on dialysis for {approx_years_on_dialysis:.1f} years on {self.sim.date}") - self.sim.modules['Demography'].do_death( individual_id=person_id, cause="chronic_kidney_disease", originating_module=self.module ) - # Reset the flag since person is now dead + # Reset the flags since person is now dead df.at[person_id, 'ckd_death_event_scheduled'] = False + df.at[person_id, 'ckd_in_renal_clinic'] = False else: # If person survived this check, reschedule for another check in 6 months next_check_date = self.sim.date + DateOffset(months=6) self.sim.schedule_event( - CKD_DialysisDeathEvent(self.module, person_id), + CKD_Renal_Clinic_DeathEvent(self.module, person_id), next_check_date ) @@ -842,8 +841,6 @@ def apply(self, person_id, squeeze_factor): if person["ckd_on_treatment"] or not person["ckd_diagnosed"]: return self.sim.modules["HealthSystem"].get_blank_appt_footprint() - assert pd.isnull(df.at[person_id, 'ckd_date_treatment']) - is_cons_available = self.get_consumables( self.module.cons_item_codes['renal_consumables'] ) @@ -857,9 +854,12 @@ def apply(self, person_id, squeeze_factor): # record date of treatment #df.at[person_id, 'ckd_on_treatment'] = True df.at[person_id, 'ckd_in_renal_clinic'] = True - df.at[person_id, 'ckd_date_treatment'] = self.sim.date + # df.at[person_id, 'ckd_date_treatment'] = self.sim.date + # Set renal clinic start date if patient is new + if pd.isna(df.at[person_id, 'ckd_date_started_renal_clinic']): + df.at[person_id, 'ckd_date_started_renal_clinic'] = self.sim.date df.at[person_id, 'ckd_stage_at_which_treatment_given'] = df.at[person_id, 'ckd_status'] - self.add_equipment({'Weighing scale', 'Blood pressure machine', 'Purple blood bottle', 'Red blood bottle' + self.add_equipment({'Weighing scale', 'Blood pressure machine', 'Purple blood bottle', 'Red blood bottle', 'Ultrasound scanning machine', 'Electrocardiogram', 'Oxygen concentrator'}) if renal_medication_successful: next_session = self.sim.date + pd.DateOffset(months=1) @@ -930,39 +930,38 @@ def apply(self, person_id, squeeze_factor): # Set person to be on dialysis df.at[person_id, 'nc_ckd_on_dialysis'] = True - -# class HSI_Haemodialysis_Refill(HSI_Event, IndividualScopeEventMixin): -# """This is an event in which a person goes for dialysis sessions 2 times a week -# adding up to 8 times a month.""" -# -# def __init__(self, module, person_id): -# super().__init__(module, person_id=person_id) -# -# self.TREATMENT_ID = 'CKD_Treatment_Haemodialysis' -# self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) -# self.ACCEPTED_FACILITY_LEVEL = '3' -# -# def apply(self, person_id, squeeze_factor): -# df = self.sim.population.props -# -# if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'nc_chronic_kidney_disease']: -# return self.sim.modules['HealthSystem'].get_blank_appt_footprint() -# -# if not df.at[person_id, 'nc_ckd_on_dialysis']: -# df.at[person_id, 'nc_ckd_on_dialysis'] = True -# -# # Increment total number of dialysis sessions the person has ever had in their lifetime -# df.at[person_id, 'nc_ckd_total_dialysis_sessions'] += 1 -# -# self.add_equipment({'Chair', 'Dialysis machine', 'Dialyser (Artificial Kidney)', -# 'Bloodlines', 'Dialysate solution', 'Dialysis water treatment system'}) -# -# next_session_date = self.sim.date + pd.DateOffset(days=3) -# self.sim.modules['HealthSystem'].schedule_hsi_event(self, -# topen=next_session_date, -# tclose=next_session_date + pd.DateOffset(days=1), -# priority=1 -# ) + # class HSI_Haemodialysis_Refill(HSI_Event, IndividualScopeEventMixin): + # """This is an event in which a person goes for dialysis sessions 2 times a week + # adding up to 8 times a month.""" + # + # def __init__(self, module, person_id): + # super().__init__(module, person_id=person_id) + # + # self.TREATMENT_ID = 'CKD_Treatment_Haemodialysis' + # self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) + # self.ACCEPTED_FACILITY_LEVEL = '3' + # + # def apply(self, person_id, squeeze_factor): + # df = self.sim.population.props + # + # if not df.at[person_id, 'is_alive'] or not df.at[person_id, 'nc_chronic_kidney_disease']: + # return self.sim.modules['HealthSystem'].get_blank_appt_footprint() + # + # if not df.at[person_id, 'nc_ckd_on_dialysis']: + # df.at[person_id, 'nc_ckd_on_dialysis'] = True + # + # # Increment total number of dialysis sessions the person has ever had in their lifetime + # df.at[person_id, 'nc_ckd_total_dialysis_sessions'] += 1 + # + # self.add_equipment({'Chair', 'Dialysis machine', 'Dialyser (Artificial Kidney)', + # 'Bloodlines', 'Dialysate solution', 'Dialysis water treatment system'}) + # + # next_session_date = self.sim.date + pd.DateOffset(days=3) + # self.sim.modules['HealthSystem'].schedule_hsi_event(self, + # topen=next_session_date, + # tclose=next_session_date + pd.DateOffset(days=1), + # priority=1 + # ) def never_ran(self) -> None: """What to do if the event is never run by the HealthSystem""" @@ -1040,27 +1039,24 @@ def apply(self, person_id, squeeze_factor): ) else: - df.at[person_id, 'ckd_date_transplant'] = self.sim.date #todo Upile look at this logic, related to DALYs + df.at[ + person_id, 'ckd_date_transplant'] = self.sim.date #todo Upile look at this logic, related to DALYs # Schedule deaths #self.schedule_dialysis_death_event(person_id) - should_die_of_graft_failure = False rand_val = self.module.rng.random() should_die_of_graft_failure = rand_val < p['prob_transplant_death_by_graft_failure'] if should_die_of_graft_failure: - self.sim.modules['Demography'].do_death( - individual_id=person_id, - cause="chronic_kidney_disease", - originating_module=self.module) - - else: - next_scheduled_HSI = cardio_metabolic_disorders.HSI_CardioMetabolicDisorders_Dialysis_Refill \ - if (self.module.rng.random_sample() < - p['prob_going_for_refill_or_evaluation_from_graft_failure']) else HSI_Kidney_Transplant_Evaluation - hs.schedule_hsi_event( - hsi_event=next_scheduled_HSI(module=self.module, person_id=person_id), - topen=self.sim.date, - priority=0 - ) + (self.sim.modules['Demography']. + do_death(individual_id=person_id, cause="chronic_kidney_disease", originating_module=self.module)) + + else: + next_scheduled_hsi = cardio_metabolic_disorders.HSI_CardioMetabolicDisorders_Dialysis_Refill \ + if (self.module.rng.random_sample() < + p[ + 'prob_going_for_refill_or_evaluation_from_graft_failure']) else HSI_Kidney_Transplant_Evaluation + hs.schedule_hsi_event( + hsi_event=next_scheduled_hsi(module=self.module, person_id=person_id), + priority=0) class HSI_AntiRejectionDrug_Refill(HSI_Event, IndividualScopeEventMixin):