diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv index 33ba052c64..45f801f0c8 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:3e151e16f7eea2ae61d2fa637c26449aa533ddc6a7f0d83aff495f5f6c9d1f8d -size 33201 +oid sha256:ec5f619816df6150ae92839152607296a5f2289024c92ce6b5ba621d38db20b7 +size 33517 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 3f0739577a..706297da67 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:e31936377f6b90779ad66480c4bc477cdca9322e86f2e00d202bbb91eebf6d57 -size 1306170 +oid sha256:2785365d20a4da4c147ba6a5df9e0259c9076df0fec556086aea0f2a068c9c53 +size 1313098 diff --git a/src/scripts/data_file_processing/healthsystem/equipment/equipment_availability_estimation.py b/src/scripts/data_file_processing/healthsystem/equipment/equipment_availability_estimation.py index 0a742d37b8..12ba3c7f9d 100644 --- a/src/scripts/data_file_processing/healthsystem/equipment/equipment_availability_estimation.py +++ b/src/scripts/data_file_processing/healthsystem/equipment/equipment_availability_estimation.py @@ -322,6 +322,12 @@ .drop_duplicates() \ .pipe(lambda x: x.set_index(x['Item_code'].astype(int)))['Category'] \ .to_dict() +# Manually declare the price category for equipment items added manually +# 402: Endoscope: 'Cost >= $1000' +equipment_price_category_mapper[402] = 'Cost >= $1000' +# 403: Electrocardiogram: 'Cost >= $1000' +equipment_price_category_mapper[403] = 'Cost >= $1000' + equipment_price_category = final_equipment_availability_export_full.index.get_level_values('Item_Code') \ .map(equipment_price_category_mapper) final_equipment_availability_export_full = final_equipment_availability_export_full.groupby( diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index 8c1ab41401..c27a54dd30 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2551,6 +2551,8 @@ def _get_disease_classification_for_treatment_decision(self, 'chest_indrawing_pneumonia', (symptoms-based assessment) 'cough_or_cold' (symptoms-based assessment) }.""" + if use_oximeter: + self.add_equipment({'Pulse oximeter'}) child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) @@ -2606,6 +2608,15 @@ def _try_treatment(antibiotic_indicated: Tuple[str], oxygen_indicated: bool) -> oxygen_available = self._get_cons('Oxygen_Therapy') oxygen_provided = (oxygen_available and oxygen_indicated) + # If individual is provided with oxygen, add used equipment + if oxygen_provided: + self.add_equipment({'Oxygen cylinder, with regulator', 'Nasal Prongs'}) + + # If individual is provided with intravenous antibiotics, add used equipment + if antibiotic_provided in ('1st_line_IV_antibiotics', + 'Benzylpenicillin_gentamicin_therapy_for_severe_pneumonia'): + self.add_equipment({'Infusion pump', 'Drip stand'}) + all_things_needed_available = antibiotic_available and ( (oxygen_available and oxygen_indicated) or (not oxygen_indicated) ) @@ -2687,6 +2698,7 @@ def _provide_bronchodilator_if_wheeze(self, facility_level, symptoms): if facility_level == '1a': _ = self._get_cons('Inhaled_Brochodilator') else: + # n.b. this is never called, see issue 1172 _ = self._get_cons('Brochodilator_and_Steroids') def do_on_follow_up_following_treatment_failure(self): @@ -2694,9 +2706,12 @@ def do_on_follow_up_following_treatment_failure(self): A further drug will be used but this will have no effect on the chance of the person dying.""" if self._has_staph_aureus(): - _ = self._get_cons('2nd_line_Antibiotic_therapy_for_severe_staph_pneumonia') + cons_avail = self._get_cons('2nd_line_Antibiotic_therapy_for_severe_staph_pneumonia') else: - _ = self._get_cons('Ceftriaxone_therapy_for_severe_pneumonia') + cons_avail = self._get_cons('Ceftriaxone_therapy_for_severe_pneumonia') + + if cons_avail: + self.add_equipment({'Infusion pump', 'Drip stand'}) def apply(self, person_id, squeeze_factor): """Assess and attempt to treat the person.""" diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 78899d4705..113d19fde2 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -725,14 +725,14 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check consumables are available - # TODO: replace with cystoscope - cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_biopsy_core'], - optional_item_codes= - self.module.item_codes_bladder_can['screening_biopsy_optional']) + cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_cystoscopy_core'], + optional_item_codes=self.module.item_codes_bladder_can[ + 'screening_biopsy_endoscopy_cystoscopy_optional']) if cons_avail: # Use a biopsy to diagnose whether the person has bladder Cancer - # If consumables are available, run the dx_test representing the biopsy + # If consumables are available update the use of equipment and run the dx_test representing the biopsy + self.add_equipment({'Cystoscope', 'Ordinary Microscope', 'Ultrasound scanning machine'}) # Use a cystoscope to diagnose whether the person has bladder Cancer: dx_result = hs.dx_manager.run_dx_test( @@ -798,14 +798,14 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check consumables are available - # TODO: replace with cystoscope - cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_biopsy_core'], + cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_cystoscopy_core'], optional_item_codes=self.module.item_codes_bladder_can[ - 'screening_biopsy_optional']) + 'screening_biopsy_endoscopy_cystoscopy_optional']) if cons_avail: # Use a biopsy to diagnose whether the person has bladder Cancer - # If consumables are available, run the dx_test representing the biopsy + # If consumables are available log the use of equipment and run the dx_test representing the biopsy + self.add_equipment({'Cystoscope', 'Ordinary Microscope', 'Ultrasound scanning machine'}) # Use a cystoscope to diagnose whether the person has bladder Cancer: dx_result = hs.dx_manager.run_dx_test( @@ -894,7 +894,8 @@ def apply(self, person_id, squeeze_factor): self.module.item_codes_bladder_can['treatment_surgery_optional']) if cons_avail: - # If consumables are available and the treatment will go ahead + # If consumables are available and the treatment will go ahead - update the equipment + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) # Record date and stage of starting treatment df.at[person_id, "bc_date_treatment"] = self.sim.date @@ -998,7 +999,8 @@ def apply(self, person_id, squeeze_factor): item_codes=self.module.item_codes_bladder_can['palliation']) if cons_available: - # If consumables are available and the treatment will go ahead + # If consumables are available and the treatment will go ahead - update the equipment + self.add_equipment({'Infusion pump', 'Drip stand'}) # Record the start of palliative care if this is first appointment if pd.isnull(df.at[person_id, "bc_date_palliative_care"]): diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 9ef5dc3e41..d362f7ce08 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -696,11 +696,13 @@ def apply(self, person_id, squeeze_factor): # Check consumables to undertake biopsy are available cons_avail = self.get_consumables(item_codes=self.module.item_codes_breast_can['screening_biopsy_core'], optional_item_codes= - self.module.item_codes_breast_can['screening_biopsy_optional']) + self.module.item_codes_breast_can[ + 'screening_biopsy_endoscopy_cystoscopy_optional']) if cons_avail: # Use a biopsy to diagnose whether the person has breast Cancer - # If consumables are available, run the dx_test representing the biopsy + # If consumables are available, add the used equipment and run the dx_test representing the biopsy + self.add_equipment({'Ultrasound scanning machine', 'Ordinary Microscope'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -764,8 +766,6 @@ def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] - # todo: request consumables needed for this - if not df.at[person_id, 'is_alive']: return hs.get_blank_appt_footprint() @@ -798,7 +798,9 @@ def apply(self, person_id, squeeze_factor): ) if cons_available: - # If consumables, treatment will go ahead + # If consumables are available and the treatment will go ahead - add the used equipment + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) + # Log the use of adjuvant chemotherapy self.get_consumables( item_codes=self.module.item_codes_breast_can['treatment_chemotherapy'], @@ -906,7 +908,8 @@ def apply(self, person_id, squeeze_factor): item_codes=self.module.item_codes_breast_can['palliation']) if cons_available: - # If consumables are available and the treatment will go ahead + # If consumables are available and the treatment will go ahead - add the used equipment + self.add_equipment({'Infusion pump', 'Drip stand'}) # Record the start of palliative care if this is first appointment if pd.isnull(df.at[person_id, "brc_date_palliative_care"]): diff --git a/src/tlo/methods/cancer_consumables.py b/src/tlo/methods/cancer_consumables.py index 2649626b2f..e26d577242 100644 --- a/src/tlo/methods/cancer_consumables.py +++ b/src/tlo/methods/cancer_consumables.py @@ -15,16 +15,16 @@ def get_consumable_item_codes_cancers(self) -> Dict[str, int]: cons_dict = dict() # Add items that are needed for all cancer modules - cons_dict['screening_biopsy_core'] = \ - {get_item_code("Biopsy needle"): 1} - - cons_dict['screening_biopsy_optional'] = \ + cons_dict['screening_biopsy_endoscopy_cystoscopy_optional'] = \ {get_item_code("Specimen container"): 1, get_item_code("Lidocaine HCl (in dextrose 7.5%), ampoule 2 ml"): 1, get_item_code("Gauze, absorbent 90cm x 40m_each_CMST"): 30, get_item_code("Disposables gloves, powder free, 100 pieces per box"): 1, get_item_code("Syringe, needle + swab"): 1} + cons_dict['screening_biopsy_core'] = \ + {get_item_code("Biopsy needle"): 1} + cons_dict['treatment_surgery_core'] = \ {get_item_code("Halothane (fluothane)_250ml_CMST"): 100, get_item_code("Scalpel blade size 22 (individually wrapped)_100_CMST"): 1} @@ -69,23 +69,9 @@ def get_consumable_item_codes_cancers(self) -> Dict[str, int]: cons_dict['screening_cystoscopy_core'] = \ {get_item_code("Cystoscope"): 1} - cons_dict['screening_cystoscope_optional'] = \ - {get_item_code("Specimen container"): 1, - get_item_code("Lidocaine HCl (in dextrose 7.5%), ampoule 2 ml"): 1, - get_item_code("Gauze, absorbent 90cm x 40m_each_CMST"): 30, - get_item_code("Disposables gloves, powder free, 100 pieces per box"): 1, - get_item_code("Syringe, needle + swab"): 1} - elif 'OesophagealCancer' == self.name: - cons_dict['screening_endoscope_core'] = \ + cons_dict['screening_endoscopy_core'] = \ {get_item_code("Endoscope"): 1} - cons_dict['screening_endoscope_optional'] = \ - {get_item_code("Specimen container"): 1, - get_item_code("Gauze, absorbent 90cm x 40m_each_CMST"): 30, - get_item_code("Lidocaine HCl (in dextrose 7.5%), ampoule 2 ml"): 1, - get_item_code("Disposables gloves, powder free, 100 pieces per box"): 1, - get_item_code("Syringe, needle + swab"): 1} - return cons_dict diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index c46ea7b37e..d90688adb0 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1435,6 +1435,7 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Run a test to diagnose whether the person has condition: + self.add_equipment({'Blood pressure machine'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='assess_hypertension', hsi_event=self @@ -1487,6 +1488,9 @@ def do_for_each_condition(self, _c) -> bool: if df.at[person_id, f'nc_{_c}_ever_diagnosed']: return + if _c == 'chronic_ischemic_heart_disease': + self.add_equipment({'Electrocardiogram', 'Stethoscope'}) + # Run a test to diagnose whether the person has condition: dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run=f'assess_{_c}', @@ -1519,6 +1523,11 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Do test and trigger treatment (if necessary) for each condition: + if set(self.conditions_to_investigate).intersection( + ['diabetes', 'chronic_kidney_disease', 'chronic_ischemic_hd'] + ): + self.add_equipment({'Analyser, Haematology', 'Analyser, Combined Chemistry and Electrolytes'}) + hsi_scheduled = [self.do_for_each_condition(_c) for _c in self.conditions_to_investigate] # If no follow-up treatment scheduled but the person has at least 2 risk factors, start weight loss treatment @@ -1542,6 +1551,7 @@ def apply(self, person_id, squeeze_factor): and (self.module.rng.rand() < self.module.parameters['hypertension_hsi']['pr_assessed_other_symptoms']) ): # Run a test to diagnose whether the person has condition: + self.add_equipment({'Blood pressure machine'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='assess_hypertension', hsi_event=self @@ -1589,6 +1599,8 @@ def apply(self, person_id, squeeze_factor): # Don't advise those with CKD to lose weight, but do so for all other conditions if BMI is higher than normal if self.condition != 'chronic_kidney_disease' and (df.at[person_id, 'li_bmi'] > 2): + self.add_equipment({'Weighing scale'}) + self.sim.population.props.at[person_id, 'nc_ever_weight_loss_treatment'] = True # Schedule a post-weight loss event for individual to potentially lose weight in next 6-12 months: self.sim.schedule_event(CardioMetabolicDisordersWeightLossEvent(m, person_id), @@ -1749,6 +1761,12 @@ def do_for_each_event_to_be_investigated(self, _ev): df = self.sim.population.props # Run a test to diagnose whether the person has condition: + if _ev == 'ever_stroke': + self.add_equipment({'Computed Tomography (CT machine)', 'CT scanner accessories'}) + + if _ev == 'ever_heart_attack': + self.add_equipment({'Electrocardiogram'}) + dx_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run=f'assess_{_ev}', hsi_event=self @@ -1808,6 +1826,7 @@ def apply(self, person_id, squeeze_factor): data=('This is HSI_CardioMetabolicDisorders_SeeksEmergencyCareAndGetsTreatment: ' f'The squeeze-factor is {squeeze_factor}.'), ) + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('ICU')) for _ev in self.events_to_investigate: self.do_for_each_event_to_be_investigated(_ev) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 4025941254..dba3bcda8e 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -747,6 +747,7 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # The process is repeated for blood pressure monitoring if self.rng.random_sample() < params['prob_intervention_delivered_bp']: + hsi_event.add_equipment({'Sphygmomanometer'}) if self.sim.modules['HealthSystem'].dx_manager.run_dx_test(dx_tests_to_run='blood_pressure_measurement', hsi_event=hsi_event): @@ -1081,20 +1082,24 @@ def gdm_screening(self, hsi_event): # If the test accurately detects a woman has gestational diabetes the consumables are recorded and # she is referred for treatment - if avail and self.sim.modules['HealthSystem'].dx_manager.run_dx_test( - dx_tests_to_run='blood_test_glucose', hsi_event=hsi_event): + if avail: + hsi_event.add_equipment({'Glucometer'}) - logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'gdm_screen'}) - mni[person_id]['anc_ints'].append('gdm_screen') + if ( + self.sim.modules['HealthSystem'].dx_manager.run_dx_test( + dx_tests_to_run='blood_test_glucose', hsi_event=hsi_event) + ): + logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'gdm_screen'}) + mni[person_id]['anc_ints'].append('gdm_screen') - # We assume women with a positive GDM screen will be admitted (if they are not already receiving - # outpatient care) - if df.at[person_id, 'ac_gest_diab_on_treatment'] == 'none': + # We assume women with a positive GDM screen will be admitted (if they are not already receiving + # outpatient care) + if df.at[person_id, 'ac_gest_diab_on_treatment'] == 'none': - # Store onset after diagnosis as daly weight is tied to diagnosis - pregnancy_helper_functions.store_dalys_in_mni(person_id, mni, 'gest_diab_onset', - self.sim.date) - df.at[person_id, 'ac_to_be_admitted'] = True + # Store onset after diagnosis as daly weight is tied to diagnosis + pregnancy_helper_functions.store_dalys_in_mni(person_id, mni, 'gest_diab_onset', + self.sim.date) + df.at[person_id, 'ac_to_be_admitted'] = True def interventions_delivered_each_visit_from_anc2(self, hsi_event): """This function contains a collection of interventions that are delivered to women every time they attend ANC @@ -1213,6 +1218,7 @@ def full_blood_count_testing(self, hsi_event): # If a woman is not truly anaemic but the FBC returns a result of anaemia, due to tests specificity, we # assume the reported anaemia is mild hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_equipment']) + hsi_event.add_equipment({'Analyser, Haematology'}) test_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run='full_blood_count_hb', hsi_event=hsi_event) @@ -1255,6 +1261,8 @@ def antenatal_blood_transfusion(self, individual_id, hsi_event): if avail and sf_check: pregnancy_helper_functions.log_met_need(self, 'blood_tran', hsi_event) + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) + # If the woman is receiving blood due to anaemia we apply a probability that a transfusion of 2 units # RBCs will correct this woman's severe anaemia if params['treatment_effect_blood_transfusion_anaemia'] > self.rng.random_sample(): @@ -1303,6 +1311,7 @@ def initiate_treatment_for_severe_hypertension(self, individual_id, hsi_event): # If they are available then the woman is started on treatment if avail: pregnancy_helper_functions.log_met_need(self, 'iv_htns', hsi_event) + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) # We assume women treated with antihypertensives would no longer be severely hypertensive- meaning they # are not at risk of death from severe gestational hypertension in the PregnancySupervisor event @@ -1341,6 +1350,7 @@ def treatment_for_severe_pre_eclampsia_or_eclampsia(self, individual_id, hsi_eve if avail and sf_check: df.at[individual_id, 'ac_mag_sulph_treatment'] = True pregnancy_helper_functions.log_met_need(self, 'mag_sulph', hsi_event) + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) def antibiotics_for_prom(self, individual_id, hsi_event): """ @@ -1363,6 +1373,7 @@ def antibiotics_for_prom(self, individual_id, hsi_event): if avail and sf_check: df.at[individual_id, 'ac_received_abx_for_prom'] = True + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) def ectopic_pregnancy_treatment_doesnt_run(self, hsi_event): """ @@ -1452,6 +1463,12 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + # Add equipment used during first ANC visit not directly related to interventions + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('ANC')) + self.add_equipment( + {'Height Pole (Stadiometer)', 'MUAC tape', + 'Ultrasound, combined 2/4 pole interferential with vacuum and dual frequency 1-3MHZ'}) + # First all women, regardless of ANC contact or gestation, undergo urine and blood pressure measurement # and depression screening self.module.screening_interventions_delivered_at_every_contact(hsi_event=self) @@ -1470,6 +1487,7 @@ def apply(self, person_id, squeeze_factor): # If the woman presents after 20 weeks she is provided interventions she has missed by presenting late if mother.ps_gestational_age_in_weeks > 19: + self.add_equipment({'Stethoscope, foetal, monaural, Pinard, plastic'}) self.module.point_of_care_hb_testing(hsi_event=self) self.module.albendazole_administration(hsi_event=self) self.module.iptp_administration(hsi_event=self) @@ -1534,7 +1552,12 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - # First we administer the administer the interventions all women will receive at this contact regardless of + # Add equipment used during ANC visit not directly related to interventions + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('ANC')) + self.add_equipment( + {'Ultrasound, combined 2/4 pole interferential with vacuum and dual frequency 1-3MHZ'}) + + # First we administer the interventions all women will receive at this contact regardless of # gestational age self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) self.module.tetanus_vaccination(hsi_event=self) @@ -1618,6 +1641,8 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('ANC')) + gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) @@ -1690,6 +1715,8 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('ANC')) + gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) @@ -1757,7 +1784,11 @@ def apply(self, person_id, squeeze_factor): self.module.anc_counter[5] += 1 df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 - # =================================== INTERVENTIONS ==================================================== + # =================================== INTERVENTIONS =================================================== + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('ANC')) + self.add_equipment( + {'Ultrasound, combined 2/4 pole interferential with vacuum and dual frequency 1-3MHZ'}) + gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) @@ -1825,6 +1856,9 @@ def apply(self, person_id, squeeze_factor): gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) # =================================== INTERVENTIONS ==================================================== + self.add_equipment({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) + self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) if mother.ps_gestational_age_in_weeks < 40: @@ -1884,6 +1918,8 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('ANC')) + gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) @@ -1937,6 +1973,8 @@ def apply(self, person_id, squeeze_factor): self.module.anc_counter[8] += 1 df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('ANC')) + self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) if df.at[person_id, 'ac_to_be_admitted']: @@ -2559,6 +2597,10 @@ def apply(self, person_id, squeeze_factor): sf='retained_prod', hsi_event=self) + # Add used equipment if intervention can happen + if baseline_cons and sf_check: + self.add_equipment({'D&C set', 'Suction Curettage machine', 'Drip stand', 'Infusion pump'}) + # Then we determine if a woman gets treatment for her complication depending on availability of the baseline # consumables (misoprostol) or a HCW who can conduct MVA/DC (we dont model equipment) and additional # consumables for management of her specific complication @@ -2643,6 +2685,7 @@ def apply(self, person_id, squeeze_factor): if avail: self.sim.modules['PregnancySupervisor'].mother_and_newborn_info[person_id]['delete_mni'] = True pregnancy_helper_functions.log_met_need(self.module, 'ep_case_mang', self) + self.add_equipment({'Laparotomy Set'}) # For women who have sought care after they have experienced rupture we use this treatment variable to # reduce risk of death (women who present prior to rupture do not pass through the death event as we assume diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 580125f7ba..ab6c633f4c 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1181,6 +1181,11 @@ def apply(self, person_id, squeeze_factor): # Record the date that Family Planning Appointment happened for this person self.sim.population.props.at[person_id, "co_date_of_last_fp_appt"] = self.sim.date + # Measure weight, height and BP even if contraception not administrated + self.add_equipment({ + 'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine' + }) + # Determine essential and optional items items_essential = self.module.cons_codes[self.new_contraceptive] items_optional = {} @@ -1206,7 +1211,8 @@ def apply(self, person_id, squeeze_factor): items_all = {**items_essential, **items_optional} # Determine whether the contraception is administrated (ie all essential items are available), - # if so do log the availability of all items, if not set the contraception to "not_using": + # if so do log the availability of all items and update used equipment if any, if not set the contraception to + # "not_using": co_administrated = all(v for k, v in cons_available.items() if k in items_essential) if co_administrated: @@ -1230,6 +1236,18 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Add used equipment + if _new_contraceptive == 'female_sterilization': + self.add_equipment({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' + }) + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Minor Surgery')) + elif _new_contraceptive == 'IUD': + self.add_equipment({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' + }) + else: _new_contraceptive = "not_using" diff --git a/src/tlo/methods/copd.py b/src/tlo/methods/copd.py index dc85f2b5d9..53602505ae 100644 --- a/src/tlo/methods/copd.py +++ b/src/tlo/methods/copd.py @@ -591,6 +591,7 @@ def apply(self, person_id, squeeze_factor): oxygen=self.get_consumables({self.module.item_codes['oxygen']: 23_040}), aminophylline=self.get_consumables({self.module.item_codes['aminophylline']: 600}) ) + self.add_equipment({'Oxygen cylinder, with regulator', 'Nasal Prongs', 'Drip stand', 'Infusion pump'}) if prob_treatment_success: df.at[person_id, 'ch_will_die_this_episode'] = False diff --git a/src/tlo/methods/diarrhoea.py b/src/tlo/methods/diarrhoea.py index 8ca36ebedb..06c8a37b18 100644 --- a/src/tlo/methods/diarrhoea.py +++ b/src/tlo/methods/diarrhoea.py @@ -13,7 +13,6 @@ Outstanding Issues * To include rotavirus vaccine - * See todo """ from __future__ import annotations @@ -1572,6 +1571,8 @@ def apply(self, person_id, squeeze_factor): if not df.at[person_id, 'is_alive']: return + self.add_equipment({'Infusion pump', 'Drip stand'}) + self.module.do_treatment(person_id=person_id, hsi_event=self) diff --git a/src/tlo/methods/equipment.py b/src/tlo/methods/equipment.py index dd86f91108..e00bf030fd 100644 --- a/src/tlo/methods/equipment.py +++ b/src/tlo/methods/equipment.py @@ -1,6 +1,6 @@ import warnings from collections import defaultdict -from typing import Counter, Iterable, Literal, Set, Union +from typing import Counter, Dict, Iterable, Literal, Set, Union import numpy as np import pandas as pd @@ -77,6 +77,7 @@ def __init__( # - Data structures for quick look-ups for items and descriptors self._item_code_lookup = self.catalogue.set_index('Item_Description')['Item_Code'].to_dict() + self._pkg_lookup = self._create_pkg_lookup() self._all_item_descriptors = set(self._item_code_lookup.keys()) self._all_item_codes = set(self._item_code_lookup.values()) self._all_fac_ids = self.master_facilities_list['Facility_ID'].unique() @@ -134,11 +135,11 @@ def parse_items(self, items: Union[int, str, Iterable[int], Iterable[str]]) -> S def check_item_codes_recognised(item_codes: set[int]): if not item_codes.issubset(self._all_item_codes): - warnings.warn(f'Item code(s) "{item_codes}" not recognised.') + warnings.warn(f'At least one item code was unrecognised: "{item_codes}".') def check_item_descriptors_recognised(item_descriptors: set[str]): if not item_descriptors.issubset(self._all_item_descriptors): - warnings.warn(f'Item descriptor(s) "{item_descriptors}" not recognised.') + warnings.warn(f'At least one item descriptor was unrecognised "{item_descriptors}".') # Make into a set if it is not one already if isinstance(items, (str, int)): @@ -248,13 +249,41 @@ def set_of_keys_or_empty_set(x: Union[set, dict]): data=row.to_dict(), ) - def lookup_item_codes_from_pkg_name(self, pkg_name: str) -> Set[int]: - """Convenience function to find the set of item_codes that are grouped under a package name in the catalogue. - It is expected that this is used by the disease module once and then the resulting equipment item_codes are - saved on the module.""" + def from_pkg_names(self, pkg_names: Union[str, Iterable[str]]) -> Set[int]: + """Convenience function to find the set of item_codes that are grouped under requested package name(s) in the + catalogue.""" + # Make into a set if it is not one already + if isinstance(pkg_names, (str, int)): + pkg_names = set([pkg_names]) + else: + pkg_names = set(pkg_names) + + item_codes = set() + for pkg_name in pkg_names: + if pkg_name in self._pkg_lookup.keys(): + item_codes.update(self._pkg_lookup[pkg_name]) + else: + raise ValueError(f'That Pkg_Name is not in the catalogue: {pkg_name=}') + + return item_codes + + def _create_pkg_lookup(self) -> Dict[str, Set[int]]: + """Create a lookup from a Package Name to a set of Item_Codes that are contained with that package. + N.B. In the Catalogue, there is one row for each Item, and the Packages to which each Item belongs (if any) + is given in a column 'Pkg_Name': if an item belongs to multiple packages, these names are separated by commas, + and if it doesn't belong to any package, then there is a NULL value.""" df = self.catalogue - if pkg_name not in df['Pkg_Name'].unique(): - raise ValueError(f'That Pkg_Name is not in the catalogue: {pkg_name=}') + # Make dataframe with columns for each package, and bools showing whether each item_code is included + pkgs = df['Pkg_Name'].replace({float('nan'): None}) \ + .str.get_dummies(sep=',') \ + .set_index(df.Item_Code) \ + .astype(bool) + + # Make dict of the form: {'Pkg_Code': } + pkg_lookup_dict = { + pkg_name.strip(): set(pkgs[pkg_name].loc[pkgs[pkg_name]].index.to_list()) + for pkg_name in pkgs.columns + } - return set(df.loc[df['Pkg_Name'] == pkg_name, 'Item_Code'].values) + return pkg_lookup_dict diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index 591ccc6e3d..d86c706217 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -2419,6 +2419,10 @@ def apply(self, person_id, squeeze_factor): # Update circumcision state df.at[person_id, "li_is_circ"] = True + # Add used equipment + self.add_equipment({'Drip stand', 'Stool, adjustable height', 'Autoclave', + 'Bipolar Diathermy Machine', 'Bed, adult', 'Trolley, patient'}) + # Schedule follow-up appts # schedule first follow-up appt, 3 days from procedure; self.sim.modules["HealthSystem"].schedule_hsi_event( diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index c924f017cc..695dbeb501 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1889,6 +1889,9 @@ def refer_for_cs(): hsi_event=hsi_event) if avail and sf_check: + # Add used equipment + hsi_event.add_equipment({'Delivery Forceps', 'Vacuum extractor'}) + pregnancy_helper_functions.log_met_need(self, f'avd_{indication}', hsi_event) # If AVD was successful then we record the mode of delivery. We use this variable to reduce @@ -2140,16 +2143,20 @@ def surgical_management_of_pph(self, hsi_event): sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='surg', hsi_event=hsi_event) - # determine if uterine preserving surgery will be successful - treatment_success_pph = params['success_rate_pph_surgery'] > self.rng.random_sample() + if avail and sf_check: + # Add used equipment + hsi_event.add_equipment(hsi_event.healthcare_system.equipment.from_pkg_names('Major Surgery')) - # If resources are available and the surgery is a success then a hysterectomy does not occur - if treatment_success_pph and avail and sf_check: - self.pph_treatment.set(person_id, 'surgery') + # determine if uterine preserving surgery will be successful + treatment_success_pph = params['success_rate_pph_surgery'] > self.rng.random_sample() - elif not treatment_success_pph and avail and sf_check: - self.pph_treatment.set(person_id, 'hysterectomy') - df.at[person_id, 'la_has_had_hysterectomy'] = True + if treatment_success_pph: + self.pph_treatment.set(person_id, 'surgery') + else: + # If the treatment is unsuccessful then women will require a hysterectomy to stop the bleeding + hsi_event.add_equipment({'Hysterectomy set'}) + self.pph_treatment.set(person_id, 'hysterectomy') + df.at[person_id, 'la_has_had_hysterectomy'] = True # log intervention delivery if self.pph_treatment.has_all(person_id, 'surgery') or df.at[person_id, 'la_has_had_hysterectomy']: @@ -2178,6 +2185,8 @@ def blood_transfusion(self, hsi_event): hsi_event=hsi_event) if avail and sf_check: + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) + mni[person_id]['received_blood_transfusion'] = True pregnancy_helper_functions.log_met_need(self, 'blood_tran', hsi_event) @@ -2201,6 +2210,9 @@ def assessment_and_treatment_of_anaemia(self, hsi_event): mother = df.loc[person_id] mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info + # Add used equipment + hsi_event.add_equipment({'Analyser, Haematology'}) + # Use dx_test function to assess anaemia status test_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run='full_blood_count_hb_pn', hsi_event=hsi_event) @@ -2927,6 +2939,11 @@ def apply(self, person_id, squeeze_factor): cons=self.module.item_codes_lab_consumables['delivery_core'], opt_cons=self.module.item_codes_lab_consumables['delivery_optional']) + # Add used equipment + self.add_equipment({'Delivery set', 'Weighing scale', 'Stethoscope, foetal, monaural, Pinard, plastic', + 'Resuscitaire', 'Sphygmomanometer', 'Tray, emergency', 'Suction machine', + 'Thermometer', 'Drip stand', 'Infusion pump'}) + # If the clean delivery kit consumable is available, we assume women benefit from clean delivery if avail: mni[person_id]['clean_birth_practices'] = True @@ -3233,6 +3250,9 @@ def apply(self, person_id, squeeze_factor): logger.debug(key='message', data="cs delivery blocked for this analysis") elif (avail and sf_check) or (mni[person_id]['cs_indication'] == 'other'): + # If intervention is delivered - add used equipment + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) + person = df.loc[person_id] logger.info(key='caesarean_delivery', data=person.to_dict()) logger.info(key='cs_indications', data={'id': person_id, @@ -3266,6 +3286,7 @@ def apply(self, person_id, squeeze_factor): # Unsuccessful repair will lead to this woman requiring a hysterectomy. Hysterectomy will also reduce # risk of death from uterine rupture but leads to permanent infertility in the simulation else: + self.add_equipment({'Hysterectomy set'}) df.at[person_id, 'la_has_had_hysterectomy'] = True # ============================= SURGICAL MANAGEMENT OF POSTPARTUM HAEMORRHAGE================================== diff --git a/src/tlo/methods/malaria.py b/src/tlo/methods/malaria.py index 50ad58db8b..bf7b5a11be 100644 --- a/src/tlo/methods/malaria.py +++ b/src/tlo/methods/malaria.py @@ -1215,6 +1215,10 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ma_date_tx'] = self.sim.date df.at[person_id, 'ma_tx_counter'] += 1 + # Add used equipment + self.add_equipment({'Drip stand', 'Haemoglobinometer', + 'Analyser, Combined Chemistry and Electrolytes'}) + # rdt is offered as part of the treatment package # Log the test: line-list of summary information about each test fever_present = 'fever' in self.sim.modules["SymptomManager"].has_what(person_id) diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index 5de96c9883..b6955ff9d7 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -460,6 +460,9 @@ def apply(self, person_id, squeeze_factor): logger.debug(key="HSI_Measles_Treatment", data=f"HSI_Measles_Treatment: giving required measles treatment to person {person_id}") + if "respiratory_symptoms" in symptoms: + self.add_equipment({'Oxygen concentrator', 'Oxygen cylinder, with regulator'}) + # modify person property which is checked when scheduled death occurs (or shouldn't occur) df.at[person_id, "me_on_treatment"] = True diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 492c37a1fe..433b21ca88 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -987,6 +987,7 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): if avail and sf_check: df.at[person_id, 'nb_supp_care_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_supportive_care', hsi_event) + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) # The same pattern is then followed for health centre care else: @@ -998,6 +999,7 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): if avail and sf_check: df.at[person_id, 'nb_inj_abx_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_abx', hsi_event) + hsi_event.add_equipment({'Drip stand', 'Infusion pump', 'Oxygen cylinder, with regulator'}) def link_twins(self, child_one, child_two, mother_id): """ diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index 104770a15f..1961aa340e 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -688,13 +688,15 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check the consumables are available - # todo: replace with endoscope? - cons_avail = self.get_consumables(item_codes=self.module.item_codes_oesophageal_can['screening_biopsy_core'], + cons_avail = self.get_consumables(item_codes=self.module.item_codes_oesophageal_can['screening_endoscopy_core'], optional_item_codes= - self.module.item_codes_oesophageal_can['screening_biopsy_optional']) + self.module.item_codes_oesophageal_can[ + 'screening_biopsy_endoscopy_cystoscopy_optional']) if cons_avail: - # If consumables are available, run the dx_test representing the biopsy + # If consumables are available add used equipment and run the dx_test representing the biopsy + # n.b. endoscope not in equipment list + self.add_equipment({'Endoscope', 'Ordinary Microscope'}) # Use an endoscope to diagnose whether the person has Oesophageal Cancer: dx_result = hs.dx_manager.run_dx_test( @@ -783,7 +785,8 @@ def apply(self, person_id, squeeze_factor): self.module.item_codes_oesophageal_can['treatment_surgery_optional']) if cons_avail: - # If consumables are available and the treatment will go ahead + # If consumables are available and the treatment will go ahead - update the equipment + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) # Log chemotherapy consumables self.get_consumables( @@ -892,7 +895,8 @@ def apply(self, person_id, squeeze_factor): item_codes=self.module.item_codes_oesophageal_can['palliation']) if cons_available: - # If consumables are available and the treatment will go ahead + # If consumables are available and the treatment will go ahead - update the equipment + self.add_equipment({'Infusion pump', 'Drip stand'}) # Record the start of palliative care if this is first appointment if pd.isnull(df.at[person_id, "oc_date_palliative_care"]): diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 774b809cfc..5999792393 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -694,10 +694,12 @@ def apply(self, person_id, squeeze_factor): # Check consumables are available cons_avail = self.get_consumables(item_codes=self.module.item_codes_other_can['screening_biopsy_core'], optional_item_codes= - self.module.item_codes_other_can['screening_biopsy_optional']) + self.module.item_codes_other_can[ + 'screening_biopsy_endoscopy_cystoscopy_optional']) if cons_avail: - # If consumables are available, run the dx_test representing the biopsy + # If consumables are available add used equipment and run the dx_test representing the biopsy + self.add_equipment({'Ultrasound scanning machine', 'Ordinary Microscope'}) # Use a diagnostic_device to diagnose whether the person has other adult cancer: dx_result = hs.dx_manager.run_dx_test( @@ -786,7 +788,8 @@ def apply(self, person_id, squeeze_factor): ) if cons_available: - # If consumables are available and the treatment will go ahead + # If consumables are available and the treatment will go ahead - update the equipment + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) # Record date and stage of starting treatment df.at[person_id, "oac_date_treatment"] = self.sim.date @@ -897,7 +900,8 @@ def apply(self, person_id, squeeze_factor): item_codes=self.module.item_codes_other_can['palliation']) if cons_available: - # If consumables are available and the treatment will go ahead + # If consumables are available and the treatment will go ahead - update the equipment + self.add_equipment({'Infusion pump', 'Drip stand'}) # Record the start of palliative care if this is first appointment if pd.isnull(df.at[person_id, "oac_date_palliative_care"]): diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 5d99968cef..25bce6013f 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -1294,6 +1294,8 @@ def apply(self, person_id, squeeze_factor): self.get_consumables(item_codes=of_repair_cons) + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) + # Log the end of disability in the MNI pregnancy_helper_functions.store_dalys_in_mni( person_id, self.sim.modules['PregnancySupervisor'].mother_and_newborn_info, diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index dabb2e0593..8bb7fd82ef 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -782,7 +782,6 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - # TODO: replace with PSA test when added to cons list cons_avail = self.get_consumables(item_codes=self.module.item_codes_prostate_can['screening_psa_test_optional']) if dx_result and cons_avail: @@ -824,9 +823,12 @@ def apply(self, person_id, squeeze_factor): cons_available = self.get_consumables(item_codes=self.module.item_codes_prostate_can['screening_biopsy_core'], optional_item_codes=self.module.item_codes_prostate_can[ - 'screening_biopsy_optional']) + 'screening_biopsy_endoscopy_cystoscopy_optional']) if cons_available: + # If consumables are available update the use of equipment and run the dx_test representing the biopsy + self.add_equipment({'Ultrasound scanning machine', 'Ordinary Microscope'}) + # Use a biopsy to assess whether the person has prostate cancer: dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_prostate_cancer', @@ -914,7 +916,8 @@ def apply(self, person_id, squeeze_factor): 'treatment_surgery_optional']) if cons_available: - # If consumables are available the treatment will go ahead + # If consumables are available and the treatment will go ahead - update the equipment + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) # Record date and stage of starting treatment df.at[person_id, "pc_date_treatment"] = self.sim.date @@ -1018,7 +1021,8 @@ def apply(self, person_id, squeeze_factor): item_codes=self.module.item_codes_prostate_can['palliation']) if cons_available: - # If consumables are available and the treatment will go ahead + # If consumables are available and the treatment will go ahead - update the equipment + self.add_equipment({'Infusion pump', 'Drip stand'}) # Record the start of palliative care if this is first appointment if pd.isnull(df.at[person_id, "pc_date_palliative_care"]): diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index d6aa1d693f..b76fb40e9f 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3213,8 +3213,13 @@ def apply(self, person_id, squeeze_factor): self.sim.population.props.at[person_id, 'rt_diagnosed'] = True road_traffic_injuries = self.sim.modules['RTI'] road_traffic_injuries.rti_injury_diagnosis(person_id, self.EXPECTED_APPT_FOOTPRINT) - if 'Tomography' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): + + if 'DiagRadio' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('X-ray')) + + elif 'Tomography' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): self.ACCEPTED_FACILITY_LEVEL = '3' + self.add_equipment({'Computed Tomography (CT machine)', 'CT scanner accessories'}) def did_not_run(self, *args, **kwargs): pass @@ -3516,6 +3521,9 @@ def apply(self, person_id, squeeze_factor): # determine the number of ICU days used to treat patient if df.loc[person_id, 'rt_ISS_score'] > self.hdu_cut_off_iss_score: + + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('ICU')) + mean_icu_days = p['mean_icu_days'] sd_icu_days = p['sd_icu_days'] mean_tbi_icu_days = p['mean_tbi_icu_days'] @@ -3808,8 +3816,6 @@ class HSI_RTI_Shock_Treatment(HSI_Event, IndividualScopeEventMixin): """ This HSI event handles the process of treating hypovolemic shock, as recommended by the pediatric handbook for Malawi and (TODO: FIND ADULT REFERENCE) - Currently this HSI_Event is described only and not used, as I still need to work out how to model the occurrence - of shock """ def __init__(self, module, person_id): @@ -3857,6 +3863,7 @@ def apply(self, person_id, squeeze_factor): logger.debug(key='rti_general_message', data=f"Hypovolemic shock treatment available for person {person_id}") df.at[person_id, 'rt_in_shock'] = False + self.add_equipment({'Infusion pump', 'Drip stand', 'Oxygen cylinder, with regulator', 'Nasal Prongs'}) else: self.sim.modules['RTI'].schedule_hsi_event_for_tomorrow(self) return self.make_appt_footprint({}) @@ -3956,6 +3963,9 @@ def apply(self, person_id, squeeze_factor): data=f"Fracture casts available for person %d's {fracturecastcounts + slingcounts} fractures, " f"{person_id}" ) + + self.add_equipment({'Casting platform', 'Casting chairs', 'Bucket, 10L'}) + # update the property rt_med_int to indicate they are recieving treatment df.at[person_id, 'rt_med_int'] = True # Find the persons injuries @@ -4092,6 +4102,9 @@ def apply(self, person_id, squeeze_factor): logger.debug(key='rti_general_message', data=f"Fracture casts available for person {person_id} {open_fracture_counts} open fractures" ) + + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) + person = df.loc[person_id] # update the dataframe to show this person is recieving treatment df.loc[person_id, 'rt_med_int'] = True @@ -4257,6 +4270,7 @@ def __init__(self, module, person_id): p = self.module.parameters self.prob_mild_burns = p['prob_mild_burns'] + def apply(self, person_id, squeeze_factor): get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name df = self.sim.population.props @@ -4810,6 +4824,9 @@ def apply(self, person_id, squeeze_factor): # RTI_Med assert df.loc[person_id, 'rt_diagnosed'], 'This person has not been through a and e' assert df.loc[person_id, 'rt_med_int'], 'This person has not been through rti med int' + + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) + # ------------------------ Track permanent disabilities with treatment ------------------------------------- # --------------------------------- Perm disability from TBI ----------------------------------------------- codes = ['133', '133a', '133b', '133c', '133d', '134', '134a', '134b', '135'] @@ -5125,6 +5142,8 @@ def apply(self, person_id, squeeze_factor): # todo: think about consequences of certain consumables not being available for minor surgery and model health # outcomes if request_outcome: + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) + # create a dictionary to store the recovery times for each injury in days minor_surg_recov_time_days = { '322': 180, diff --git a/src/tlo/methods/schisto.py b/src/tlo/methods/schisto.py index 810c869f10..0e9735286a 100644 --- a/src/tlo/methods/schisto.py +++ b/src/tlo/methods/schisto.py @@ -923,6 +923,8 @@ def apply(self, person_id, squeeze_factor): ) if will_test: + self.add_equipment({'Ordinary Microscope'}) + # Determine if they truly are infected (with any of the species) is_infected = (person.loc[cols_of_infection_status] != 'Non-infected').any() diff --git a/src/tlo/methods/symptommanager.py b/src/tlo/methods/symptommanager.py index 68edbf0840..26f6aa7ee4 100644 --- a/src/tlo/methods/symptommanager.py +++ b/src/tlo/methods/symptommanager.py @@ -272,7 +272,8 @@ def pre_initialise_population(self): SymptomManager.PROPERTIES = dict() for symptom_name in sorted(self.symptom_names): symptom_column_name = self.get_column_name_for_symptom(symptom_name) - SymptomManager.PROPERTIES[symptom_column_name] = Property(Types.BITSET, f'Presence of symptom {symptom_name}') + SymptomManager.PROPERTIES[symptom_column_name] = Property(Types.BITSET, + f'Presence of symptom {symptom_name}') def initialise_population(self, population): """ diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 053469a253..ddd2f9af43 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -1706,6 +1706,9 @@ def apply(self, person_id, squeeze_factor): ].dx_manager.run_dx_test( dx_tests_to_run="tb_clinical", hsi_event=self ) + if test_result is not None: + # Add used equipment + self.add_equipment({'Sputum Collection box', 'Ordinary Microscope'}) elif test == "xpert": @@ -1738,6 +1741,9 @@ def apply(self, person_id, squeeze_factor): dx_tests_to_run="tb_xpert_test_smear_negative", hsi_event=self, ) + if test_result is not None: + # Add used equipment + self.add_equipment({'Sputum Collection box', 'Gene Expert (16 Module)'}) # ------------------------- testing referrals ------------------------- # @@ -1756,6 +1762,9 @@ def apply(self, person_id, squeeze_factor): ACTUAL_APPT_FOOTPRINT = self.make_appt_footprint( {"Over5OPD": 2, "LabTBMicro": 1} ) + if test_result is not None: + # Add used equipment + self.add_equipment({'Sputum Collection box', 'Ordinary Microscope'}) # if still no result available, rely on clinical diagnosis if test_result is None: @@ -1953,6 +1962,8 @@ def apply(self, person_id, squeeze_factor): test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_xray_smear_negative", hsi_event=self ) + if test_result is not None: + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('X-ray')) # if consumables not available, refer to level 2 # return blank footprint as xray did not occur @@ -2026,6 +2037,8 @@ def apply(self, person_id, squeeze_factor): test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_xray_smear_negative", hsi_event=self ) + if test_result is not None: + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('X-ray')) # if consumables not available, rely on clinical diagnosis # return blank footprint as xray was not available @@ -2287,6 +2300,9 @@ def apply(self, person_id, squeeze_factor): test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_sputum_test_smear_negative", hsi_event=self ) + if test_result is not None: + # Add used equipment + self.add_equipment({'Sputum Collection box', 'Ordinary Microscope'}) # if sputum test was available and returned positive and not diagnosed with mdr, schedule xpert test if test_result and not person["tb_diagnosed_mdr"]: @@ -2301,6 +2317,9 @@ def apply(self, person_id, squeeze_factor): xperttest_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_xpert_test_smear_negative", hsi_event=self ) + if xperttest_result is not None: + # Add used equipment + self.add_equipment({'Sputum Collection box', 'Gene Expert (16 Module)'}) # if xpert test returns new mdr-tb diagnosis if xperttest_result and (df.at[person_id, "tb_strain"] == "mdr"): diff --git a/tests/test_equipment.py b/tests/test_equipment.py index a02ea282f8..1167023aa8 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -22,14 +22,18 @@ def test_core_functionality_of_equipment_class(seed): # Create toy data catalogue = pd.DataFrame( + # PkgWith0+1 stands alone or as multiple pkgs for one item; PkgWith1 is only as multiple pkgs + # for one item; PkgWith3 only stands alone [ {"Item_Description": "ItemZero", "Item_Code": 0, "Pkg_Name": 'PkgWith0+1'}, - {"Item_Description": "ItemOne", "Item_Code": 1, "Pkg_Name": 'PkgWith0+1'}, + {"Item_Description": "ItemOne", "Item_Code": 1, "Pkg_Name": 'PkgWith0+1, PkgWith1'}, {"Item_Description": "ItemTwo", "Item_Code": 2, "Pkg_Name": float('nan')}, + {"Item_Description": "ItemThree", "Item_Code": 3, "Pkg_Name": 'PkgWith3'}, ] ) data_availability = pd.DataFrame( - # item 0 is not available anywhere; item 1 is available everywhere; item 2 is available only at facility_id=1 + # item 0 is not available anywhere; item 1 is available everywhere; item 2 is available only at facility_id=1; + # item 3 is available only at facility_id=0 [ {"Item_Code": 0, "Facility_ID": 0, "Pr_Available": 0.0}, {"Item_Code": 0, "Facility_ID": 1, "Pr_Available": 0.0}, @@ -37,6 +41,8 @@ def test_core_functionality_of_equipment_class(seed): {"Item_Code": 1, "Facility_ID": 1, "Pr_Available": 1.0}, {"Item_Code": 2, "Facility_ID": 0, "Pr_Available": 0.0}, {"Item_Code": 2, "Facility_ID": 1, "Pr_Available": 1.0}, + {"Item_Code": 3, "Facility_ID": 0, "Pr_Available": 1.0}, + {"Item_Code": 3, "Facility_ID": 1, "Pr_Available": 0.0}, ] ) mfl = pd.DataFrame( @@ -76,6 +82,22 @@ def test_core_functionality_of_equipment_class(seed): with pytest.warns(): eq_default.parse_items('ItemThatIsNotDefined') + # Lookup the item_codes that belong in a particular package. + # - When package is recognised + # if items are in the same package (once standing alone, once within multiple pkgs defined for item) + assert {0, 1} == eq_default.from_pkg_names(pkg_names='PkgWith0+1') + # if the pkg within multiple pkgs defined for item + assert {1} == eq_default.from_pkg_names(pkg_names='PkgWith1') + # if the pkg only stands alone + assert {3} == eq_default.from_pkg_names(pkg_names='PkgWith3') + # Lookup the item_codes that belong to multiple specified packages. + assert {0, 1, 3} == eq_default.from_pkg_names(pkg_names={'PkgWith0+1', 'PkgWith3'}) + assert {1, 3} == eq_default.from_pkg_names(pkg_names={'PkgWith1', 'PkgWith3'}) + + # - When package is not recognised (should raise an error) + with pytest.raises(ValueError): + eq_default.from_pkg_names(pkg_names='') + # Testing checking on available of items # - calling when all items available (should be true) assert eq_default.is_all_items_available(item_codes={1, 2}, facility_id=1) @@ -132,19 +154,11 @@ def test_core_functionality_of_equipment_class(seed): # - Check that internal record is as expected assert {0: {0: 1, 1: 2}, 1: {0: 1, 1: 1}} == dict(eq_default._record_of_equipment_used_by_facility_id) - # Lookup the item_codes that belong in a particular package. - # - When package is recognised - assert {0, 1} == eq_default.lookup_item_codes_from_pkg_name(pkg_name='PkgWith0+1') # these items are in the same - # package - # - Error thrown when package is not recognised - with pytest.raises(ValueError): - eq_default.lookup_item_codes_from_pkg_name(pkg_name='') - - equipment_item_code_that_is_available = [0, 1, ] equipment_item_code_that_is_not_available = [2, 3,] + def run_simulation_and_return_log( seed, tmpdir, equipment_in_init, equipment_in_apply ) -> Dict: