From 64e9918e8d4d8b83fe8c1860688eafe4eda6c35b Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 27 Aug 2023 23:38:26 +0100 Subject: [PATCH 001/443] utils: typo --- src/tlo/analysis/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index 2504493a4d..a9e455a455 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -265,7 +265,7 @@ def get_multiplier(_draw, _run): )['tlo.methods.population']['scaling_factor']['scaling_factor'].values[0] if custom_generate_series is None: - # If there is no `custom_generate_series` provided, it implies that function required selects a the specified + # If there is no `custom_generate_series` provided, it implies that function required selects the specified # column from the dataframe. assert column is not None, "Must specify which column to extract" From 564762552552cdac39deeb18b4a148bbf8d6c66f Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 30 Aug 2023 23:39:44 +0100 Subject: [PATCH 002/443] healthsystem: typo --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index c91a6f970d..102d907592 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -353,7 +353,7 @@ def initialise(self): # If there are bed-days specified, add (if needed) the in-patient admission and in-patient day Appointment # Types. # (HSI that require a bed for one or more days always need such appointments, but this may have been - # missed in the declaration of the `EXPECTED_APPPT_FOOTPRINT` in the HSI.) + # missed in the declaration of the `EXPECTED_APPT_FOOTPRINT` in the HSI.) # NB. The in-patient day Appointment time is automatically applied on subsequent days. if sum(self.BEDDAYS_FOOTPRINT.values()): self.EXPECTED_APPT_FOOTPRINT = health_system.bed_days.add_first_day_inpatient_appts_to_footprint( From 395285ccb00e2bceb8c5812b27ac3bdcc96215e9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 12:11:31 +0100 Subject: [PATCH 003/443] TODO: some modules use equipment when talking about consumables --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 ++ src/tlo/methods/labour.py | 2 ++ src/tlo/methods/newborn_outcomes.py | 2 ++ src/tlo/methods/rti.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index a6c51295f0..71c242cee0 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -216,6 +216,8 @@ def get_and_store_pregnancy_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_preg_consumables['blood_test_equipment'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 0740393a53..7c490964dd 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -692,6 +692,8 @@ def get_and_store_labour_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_lab_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 6bc2e1896e..56c56a34f1 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -410,6 +410,8 @@ def get_and_store_newborn_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_nb_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index aac0129cf6..0d788aaf6c 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4665,6 +4665,8 @@ def apply(self, person_id, squeeze_factor): # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, # equipment used by surgeon, gloves and facemask + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From 1b51696b506024a2d5258958fd1851180d24c375 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 12:32:56 +0100 Subject: [PATCH 004/443] co: comments about consumable items --- src/tlo/methods/contraception.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index c19560f457..5a4b283af7 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -744,6 +744,7 @@ def get_item_code_for_each_contraceptive(self): get_items_from_pkg = self.sim.modules['HealthSystem'].get_item_codes_from_package_name _cons_codes = dict() + # items for each method that requires an HSI to switch to _cons_codes['pill'] = get_items_from_pkg('Pill') _cons_codes['male_condom'] = get_items_from_pkg('Male condom') _cons_codes['other_modern'] = get_items_from_pkg('Female Condom') @@ -753,6 +754,7 @@ def get_item_code_for_each_contraceptive(self): _cons_codes['implant'] = get_items_from_pkg('Implant') _cons_codes['female_sterilization'] = get_items_from_pkg('Female sterilization') assert set(_cons_codes.keys()) == set(self.states_that_may_require_HSI_to_switch_to) + # items used when initiating a modern reliable method after not using or switching from non-reliable method _cons_codes['co_initiation'] = get_items_from_pkg('Contraception initiation') return _cons_codes From 16a6680b6783d1e0d7c6f9bb6208feb03f956414 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 14:52:20 +0100 Subject: [PATCH 005/443] healthsyst: comment moved to where it belongs --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 102d907592..fa86944192 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2649,13 +2649,13 @@ def _reset_internal_stores(self) -> None: self._treatment_ids = defaultdict(int) # Running record of the `TREATMENT_ID`s of `HSI_Event`s self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} + # <--Same as `self._appts` but also split by facility_level # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran self._never_ran_appts = defaultdict(int) # As above, but for `HSI_Event`s that have never ran self._never_ran_appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} - # <--Same as `self._appts` but also split by facility_level self._frac_time_used_overall = [] # Running record of the usage of the healthcare system self._squeeze_factor_by_hsi_event_name = defaultdict(list) # Running record the squeeze-factor applying to each # treatment_id. Key is of the form: From 34d5e50b29fd3790748c56ce91cf5793bb5e37da Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:11:12 +0100 Subject: [PATCH 006/443] breast_cancer: TODOs to ask, dummy used_equipment added where Andrew requested --- src/tlo/methods/breast_cancer.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 7760340088..9b5abcf8aa 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -575,6 +575,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. + # TODO: Is it? There is 1 month as the frequency, isn't it? def apply(self, population): df = population.props # shortcut to dataframe @@ -646,6 +647,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -666,6 +668,8 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -759,6 +763,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Record used equipment + self.used_equipment = 'Anything used for mastectomy as I guess this is about' + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( From 66b0e66839182b13c38e83bc879251775a81285a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:15:40 +0100 Subject: [PATCH 007/443] co: dummy used_equipment added for methods where Emi listed some --- src/tlo/methods/contraception.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 5a4b283af7..efc5164de1 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1263,7 +1263,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 record 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: @@ -1287,6 +1288,13 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Record used equipment when needed + if _new_contraceptive == 'female_sterilization': + self.used_equipment = {'Smt used to sterilize a woman'} + elif _new_contraceptive == 'IUD': + self.used_equipment = {'Equipment used when performing IUD'} + else: _new_contraceptive = "not_using" From 3631ad84e783f771c758757f980f059ae01fa89f Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:24:02 +0100 Subject: [PATCH 008/443] healthsystem: annual equipment summary log by fac. level --- src/tlo/methods/healthsystem.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index fa86944192..345e2ac5f6 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,6 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + self.used_equipment = set() @property def bed_days_allocated_to_this_event(self): @@ -1730,6 +1731,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, + equipment=hsi_event.used_equipment, ) def write_to_hsi_log( @@ -1740,6 +1742,7 @@ def write_to_hsi_log( squeeze_factor: float, did_run: bool, priority: int, + equipment: set, ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1769,6 +1772,7 @@ def write_to_hsi_log( squeeze_factor=squeeze_factor, appt_footprint=event_details.appt_footprint, level=event_details.facility_level, + equipment=equipment, ) def call_and_record_never_ran_hsi_event(self, hsi_event, priority=None): @@ -2596,6 +2600,7 @@ def apply(self, population): squeeze_factor=0.0, priority=-1, did_run=True, + equipment=set() # TODO: explore more, should it be non-emtpy in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2650,6 +2655,7 @@ def _reset_internal_stores(self) -> None: self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} # <--Same as `self._appts` but also split by facility_level + self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran @@ -2666,7 +2672,8 @@ def record_hsi_event(self, hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str + level: str, + equipment: set ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2683,6 +2690,9 @@ def record_hsi_event(self, self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number + # Update used equipment by level + self._equip_by_level[level].update(equipment) + def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2745,6 +2755,14 @@ def write_to_log_and_reset_counters(self): }, ) + logger_summary.info( + key="Equipment", + description="Sets of used equipment for each facility level in this calendar year.", + data={ + "Equipment_By_Level": self._equip_by_level, + }, + ) + self._reset_internal_stores() From 3f9287ad1e5b4ada820c74ccb7e9a10d18520273 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:35:32 +0100 Subject: [PATCH 009/443] long_run_all_dis: 2K pop, 1 run -- to see results fast --- .../calibration_analyses/scenarios/long_run_all_diseases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py index 8df04fc12e..756af3da53 100644 --- a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py +++ b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py @@ -22,9 +22,9 @@ def __init__(self): self.seed = 0 self.start_date = Date(2010, 1, 1) self.end_date = Date(2030, 1, 1) - self.pop_size = 20_000 + self.pop_size = 2_000 self.number_of_draws = 1 - self.runs_per_draw = 10 + self.runs_per_draw = 1 def log_configuration(self): return { From 33853d1a4f4e5273844527d516af8e3190831149 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 16:12:18 +0100 Subject: [PATCH 010/443] Revert "long_run_all_dis: 2K pop, 1 run -- to see results fast" This reverts commit 6d054237a0d44cd0e57c4e46ba21e1332a9c9777. --- .../calibration_analyses/scenarios/long_run_all_diseases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py index 756af3da53..8df04fc12e 100644 --- a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py +++ b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py @@ -22,9 +22,9 @@ def __init__(self): self.seed = 0 self.start_date = Date(2010, 1, 1) self.end_date = Date(2030, 1, 1) - self.pop_size = 2_000 + self.pop_size = 20_000 self.number_of_draws = 1 - self.runs_per_draw = 1 + self.runs_per_draw = 10 def log_configuration(self): return { From eee74a4c6ac3f5f2d38872aacb4fac04aa783526 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 16:15:18 +0100 Subject: [PATCH 011/443] breast_cancer: mastectomy dummy equipment fixed --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 9b5abcf8aa..d49bda71df 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -764,7 +764,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] # Record used equipment - self.used_equipment = 'Anything used for mastectomy as I guess this is about' + self.used_equipment = {'Anything used for mastectomy as I guess this is about'} # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( From 6ffd2953fd9f890e356d661c9ea432785204a705 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Sep 2023 21:01:35 +0100 Subject: [PATCH 012/443] equipment_catalogue & utils: new script + a change in utils.py - to create equip. catalogue --- .../equipment/equipment_catalogue.py | 101 ++++++++++++++++++ src/tlo/analysis/utils.py | 8 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/scripts/healthsystem/equipment/equipment_catalogue.py diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py new file mode 100644 index 0000000000..c88a469c2f --- /dev/null +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -0,0 +1,101 @@ +import argparse +import pandas as pd +from pathlib import Path +from tlo.analysis.utils import extract_results +from functools import reduce + + +def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: + """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated + year.""" + + def get_equipment_declaration_by_levels(_df): + """Get the equipment declaration by facility levels for the year.""" + + def unpack_dict_in_series(_raw: pd.Series): + # Create an empty DataFrame to store the data + df = pd.DataFrame() + + # Iterate through the dictionary items + for col_name, mydict in _raw.items(): + for date, inner_dict in mydict.items(): + # Convert the inner_dict to a list of dictionaries with 'date' + data = [{'date': date, 'fac_level': inner_dict_key, 'value': inner_dict_set} for + inner_dict_key, inner_dict_set in inner_dict.items()] + # Create a DataFrame from the list with date & fac_level as indexes + temp_df = pd.DataFrame(data) + temp_df.set_index(['date', 'fac_level'], inplace=True) + temp_df.columns = [None] + + # Concatenate the temporary DataFrame to the result DataFrame + df = pd.concat([df, temp_df]) + + # print(f"\ndf\n {df}") + df.columns = [None] + + return df + + return _df \ + .set_index('date') \ + .pipe(unpack_dict_in_series) \ + .stack() \ + .droplevel(level=2) + + return extract_results( + results_folder, + module='tlo.methods.healthsystem.summary', + key='Equipment', + custom_generate_series=get_equipment_declaration_by_levels + ) + + +def create_equipment_catalogue(results_folder: Path, output_folder: Path): + # Declare path for output file from this script + output_file_name = 'equipment_catalogue_by_level.csv' + output_detailed_file_name = 'equipment_catalogue_by_date_level_sim.csv' + + sim_equipment = get_annual_equipment_declarations_by_levels(results_folder) + sim_equipment_df = pd.DataFrame(sim_equipment) + sim_equipment_df.index.names = ['date', 'fac_level'] + + # Save the detailed CSV + sim_equipment_df.to_csv(output_folder / output_detailed_file_name) + print('equipment_catalogue_by_date_level_sim.csv saved.') + + # Prepare a catalogue only by facility levels + # Define a custom aggregation function to combine sets in columns for each row + def combine_sets(row): + combined_set = set() + for col in row: + combined_set.update(col) + return combined_set + + # Apply the custom aggregation function to each row + sim_equipment_by_level_df = sim_equipment_df.copy() + sim_equipment_by_level_df['equipment'] = sim_equipment_by_level_df.apply(combine_sets, axis=1) + # Group by 'fac_level' and join rows with the same 'fac_level' into one set + sim_equipment_by_level_df.reset_index(inplace=True) + sim_equipment_by_level_df = sim_equipment_by_level_df.groupby('fac_level')['equipment'].apply( + lambda x: list(set.union(*x)) + ).reset_index() + + # Explode the 'equipment' column to separate elements into rows + sim_equipment_by_level_df = sim_equipment_by_level_df.explode('equipment', ignore_index=True).set_index('fac_level') + + # Save the CSV equipment catalogue + sim_equipment_by_level_df.to_csv(output_folder / output_file_name) + print('equipment_catalogue_by_level.csv saved.') + + return 0 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("results_folder", type=Path) + args = parser.parse_args() + + create_equipment_catalogue( + results_folder=args.results_folder, + output_folder=args.results_folder, + ) +# NB. Edit run configuration, the Parameters: "./outputs/sejjej5@ucl.ac.uk/long_run_all_diseases-2023-09-04T233551Z" diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index a9e455a455..a8e7471233 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -282,6 +282,9 @@ def get_multiplier(_draw, _run): # get number of draws and numbers of runs info = get_scenario_info(results_folder) + def is_number(element): + return isinstance(element, (int, float)) + # Collect results from each draw/run res = dict() for draw in range(info['number_of_draws']): @@ -293,7 +296,10 @@ def get_multiplier(_draw, _run): df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key] output_from_eval: pd.Series = _gen_series(df) assert pd.Series == type(output_from_eval), 'Custom command does not generate a pd.Series' - res[draw_run] = output_from_eval * get_multiplier(draw, run) + if output_from_eval.apply(is_number).all(): + res[draw_run] = output_from_eval * get_multiplier(draw, run) + else: + res[draw_run] = output_from_eval except KeyError: # Some logs could not be found - probably because this run failed. From 6c885d751dce248c274a01501dd98451f9a0b38e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 7 Sep 2023 17:12:45 +0100 Subject: [PATCH 013/443] equipment_catalogue: PEP8 --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index c88a469c2f..fdaca54515 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -1,8 +1,9 @@ import argparse -import pandas as pd from pathlib import Path + +import pandas as pd + from tlo.analysis.utils import extract_results -from functools import reduce def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: From 6dbb33daeb7e1351fe364df64f3ef2502a857a54 Mon Sep 17 00:00:00 2001 From: Eva Janouskova <48157464+EvaJanouskova@users.noreply.github.com> Date: Tue, 12 Sep 2023 19:53:10 +0200 Subject: [PATCH 014/443] healthsystem: sort equipment for log Co-authored-by: Tim Hallett <39991060+tbhallett@users.noreply.github.com> --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 345e2ac5f6..4f524e63a1 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2759,7 +2759,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": self._equip_by_level, + "Equipment_By_Level": sorted(self._equip_by_level), }, ) From d7c75c39c31087bc32f3d0f18cdb3e51ea379cbc Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 18:26:35 +0100 Subject: [PATCH 015/443] equipment_catalogue: comment updated --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index fdaca54515..ecbc206aba 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -8,7 +8,8 @@ def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated - year.""" + year. + NB. healthsystem.summary logger required to have been set at the level INFO or higher.""" def get_equipment_declaration_by_levels(_df): """Get the equipment declaration by facility levels for the year.""" From 8d77e18d6c819984ec3782c780587026f3b308f8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:10:01 +0100 Subject: [PATCH 016/443] rti: unified use of consumables/equipment terms --- src/tlo/methods/rti.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 0d788aaf6c..f92d751e14 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4664,9 +4664,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe @@ -5002,7 +5000,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From c1373e707e816bdbd1e22bce2e0b0cc11880fd41 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:15:44 +0100 Subject: [PATCH 017/443] hs, brc, co: used_equipment renamed to EQUIPMENT; if equip always same for HSI - set in __init__, otherwise updated in apply --- src/tlo/methods/breast_cancer.py | 9 +++++---- src/tlo/methods/contraception.py | 7 ++++--- src/tlo/methods/healthsystem.py | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index d49bda71df..5bb370816c 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -648,6 +648,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -668,8 +670,6 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -763,8 +763,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Record used equipment - self.used_equipment = {'Anything used for mastectomy as I guess this is about'} + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index efc5164de1..03d762be20 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1289,11 +1289,12 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Record used equipment when needed + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': - self.used_equipment = {'Smt used to sterilize a woman'} + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': - self.used_equipment = {'Equipment used when performing IUD'} + self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: _new_contraceptive = "not_using" diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 4f524e63a1..19481ce9ac 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,7 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - self.used_equipment = set() + self.EQUIPMENT = set() @property def bed_days_allocated_to_this_event(self): @@ -1731,7 +1731,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.used_equipment, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( From 3418842501d54df453cc3595155e71dc1a527b14 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:04 +0100 Subject: [PATCH 018/443] Revert "utils: typo" This reverts commit e55a396136f0f01155ad2d0c2ccc7676a2aea99b. --- src/tlo/analysis/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index a8e7471233..6f5e5bf288 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -265,7 +265,7 @@ def get_multiplier(_draw, _run): )['tlo.methods.population']['scaling_factor']['scaling_factor'].values[0] if custom_generate_series is None: - # If there is no `custom_generate_series` provided, it implies that function required selects the specified + # If there is no `custom_generate_series` provided, it implies that function required selects a the specified # column from the dataframe. assert column is not None, "Must specify which column to extract" From 605b8ef571297a8d0c00c3e69580d2fecc87ccfa Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:08 +0100 Subject: [PATCH 019/443] Revert "healthsystem: typo" This reverts commit 1bd9a6912e0dd1027782c785ad1e945f09f9b3a1. --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 19481ce9ac..2532f6750f 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -354,7 +354,7 @@ def initialise(self): # If there are bed-days specified, add (if needed) the in-patient admission and in-patient day Appointment # Types. # (HSI that require a bed for one or more days always need such appointments, but this may have been - # missed in the declaration of the `EXPECTED_APPT_FOOTPRINT` in the HSI.) + # missed in the declaration of the `EXPECTED_APPPT_FOOTPRINT` in the HSI.) # NB. The in-patient day Appointment time is automatically applied on subsequent days. if sum(self.BEDDAYS_FOOTPRINT.values()): self.EXPECTED_APPT_FOOTPRINT = health_system.bed_days.add_first_day_inpatient_appts_to_footprint( From 153e0bab2458a8646f137fd52159a2501fa0da9b Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:15 +0100 Subject: [PATCH 020/443] Revert "co: comments about consumable items" This reverts commit 950eb7aabb9c1b1dd21958ce2590a66240060bdf. --- src/tlo/methods/contraception.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 03d762be20..e22d31f9db 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -744,7 +744,6 @@ def get_item_code_for_each_contraceptive(self): get_items_from_pkg = self.sim.modules['HealthSystem'].get_item_codes_from_package_name _cons_codes = dict() - # items for each method that requires an HSI to switch to _cons_codes['pill'] = get_items_from_pkg('Pill') _cons_codes['male_condom'] = get_items_from_pkg('Male condom') _cons_codes['other_modern'] = get_items_from_pkg('Female Condom') @@ -754,7 +753,6 @@ def get_item_code_for_each_contraceptive(self): _cons_codes['implant'] = get_items_from_pkg('Implant') _cons_codes['female_sterilization'] = get_items_from_pkg('Female sterilization') assert set(_cons_codes.keys()) == set(self.states_that_may_require_HSI_to_switch_to) - # items used when initiating a modern reliable method after not using or switching from non-reliable method _cons_codes['co_initiation'] = get_items_from_pkg('Contraception initiation') return _cons_codes From 12843fd5e58a52c46fdc305232e97c7e5f2c23c5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:59:27 +0100 Subject: [PATCH 021/443] Revert "healthsyst: comment moved to where it belongs" This reverts commit fbfada1c --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 2532f6750f..71d9b2aaaf 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2654,7 +2654,6 @@ def _reset_internal_stores(self) -> None: self._treatment_ids = defaultdict(int) # Running record of the `TREATMENT_ID`s of `HSI_Event`s self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} - # <--Same as `self._appts` but also split by facility_level self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System @@ -2662,6 +2661,7 @@ def _reset_internal_stores(self) -> None: self._never_ran_appts = defaultdict(int) # As above, but for `HSI_Event`s that have never ran self._never_ran_appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} + # <--Same as `self._appts` but also split by facility_level self._frac_time_used_overall = [] # Running record of the usage of the healthcare system self._squeeze_factor_by_hsi_event_name = defaultdict(list) # Running record the squeeze-factor applying to each # treatment_id. Key is of the form: From 9b45dfc21bca3d273312ec4f1bb243e374f85e73 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 18 Sep 2023 17:33:16 +0100 Subject: [PATCH 022/443] brc: comments updated --- src/tlo/methods/breast_cancer.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 5bb370816c..aef476c870 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -574,8 +574,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) - # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. - # TODO: Is it? There is 1 month as the frequency, isn't it? + # scheduled to run every month: do not change as this is hard-wired into the values of all the parameters. def apply(self, population): df = population.props # shortcut to dataframe @@ -646,10 +645,9 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. - # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props From cbda341a380bf734394196c5484489d6e602120e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:44:03 +0100 Subject: [PATCH 023/443] brc & co: rm the dummy examples of equipment from modules --- src/tlo/methods/breast_cancer.py | 6 ------ src/tlo/methods/contraception.py | 10 +--------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index aef476c870..1ce9ad2bf6 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,8 +646,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -761,10 +759,6 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Update equipment used with treatment - # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher - self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) - # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index e22d31f9db..bff0675d16 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1261,8 +1261,7 @@ 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 and record used equipment if any, if not set the contraception to - # "not_using": + # if so do log the availability of all items, 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: @@ -1287,13 +1286,6 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Update equipment when needed - # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher - if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) - elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Equipment used when performing IUD'}) - else: _new_contraceptive = "not_using" From 9dc6a139ab63b3ce4a163e689a708af69e03c54c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:54:51 +0100 Subject: [PATCH 024/443] brc: changes in comments moved to #1105 --- src/tlo/methods/breast_cancer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1ce9ad2bf6..7760340088 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -574,7 +574,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) - # scheduled to run every month: do not change as this is hard-wired into the values of all the parameters. + # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. def apply(self, population): df = population.props # shortcut to dataframe @@ -645,7 +645,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 6f883d37559a84200d08e6088f12a48c5a1cf353 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 18:06:37 +0100 Subject: [PATCH 025/443] [no ci] co: rm empty line (unintentionally added) --- src/tlo/methods/contraception.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index bff0675d16..c19560f457 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1285,7 +1285,6 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive - else: _new_contraceptive = "not_using" From 8f227c31f4c9a770f9d79e7748a21ab5ec563415 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 00:00:00 +0100 Subject: [PATCH 026/443] brc: dummy examples of eqip added diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 776034008..365a54ea8 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -645,7 +645,9 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -759,6 +761,10 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy'}) + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( --- src/tlo/methods/breast_cancer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 7760340088..365a54ea89 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -645,7 +645,9 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -759,6 +761,10 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy'}) + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( From a422413c75e5bf0467c5c627ac54adc7d3a535e9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 19:02:17 +0100 Subject: [PATCH 027/443] co: dummy examples of equip added --- src/tlo/methods/contraception.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index c19560f457..dc35158bb9 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1261,7 +1261,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: @@ -1285,6 +1286,14 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher + if _new_contraceptive == 'female_sterilization': + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + elif _new_contraceptive == 'IUD': + self.EQUIPMENT.update({'Equipment used when performing IUD'}) + else: _new_contraceptive = "not_using" From 785ebc2fa73075a1814111766de648d509d5ade5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 27 Aug 2023 23:38:26 +0100 Subject: [PATCH 028/443] utils: typo --- src/tlo/analysis/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index 2504493a4d..a9e455a455 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -265,7 +265,7 @@ def get_multiplier(_draw, _run): )['tlo.methods.population']['scaling_factor']['scaling_factor'].values[0] if custom_generate_series is None: - # If there is no `custom_generate_series` provided, it implies that function required selects a the specified + # If there is no `custom_generate_series` provided, it implies that function required selects the specified # column from the dataframe. assert column is not None, "Must specify which column to extract" From 1cc7cb6b6a9e1f222210868fad1659086eb15324 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 30 Aug 2023 23:39:44 +0100 Subject: [PATCH 029/443] healthsystem: typo --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 01d1160301..b42fb35b01 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -353,7 +353,7 @@ def initialise(self): # If there are bed-days specified, add (if needed) the in-patient admission and in-patient day Appointment # Types. # (HSI that require a bed for one or more days always need such appointments, but this may have been - # missed in the declaration of the `EXPECTED_APPPT_FOOTPRINT` in the HSI.) + # missed in the declaration of the `EXPECTED_APPT_FOOTPRINT` in the HSI.) # NB. The in-patient day Appointment time is automatically applied on subsequent days. if sum(self.BEDDAYS_FOOTPRINT.values()): self.EXPECTED_APPT_FOOTPRINT = health_system.bed_days.add_first_day_inpatient_appts_to_footprint( From 25438f74909fe111cdd18b9c1ba7aff6c7488d2d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 12:11:31 +0100 Subject: [PATCH 030/443] TODO: some modules use equipment when talking about consumables --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 ++ src/tlo/methods/labour.py | 2 ++ src/tlo/methods/newborn_outcomes.py | 2 ++ src/tlo/methods/rti.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index a6c51295f0..71c242cee0 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -216,6 +216,8 @@ def get_and_store_pregnancy_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_preg_consumables['blood_test_equipment'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 0740393a53..7c490964dd 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -692,6 +692,8 @@ def get_and_store_labour_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_lab_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 6bc2e1896e..56c56a34f1 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -410,6 +410,8 @@ def get_and_store_newborn_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_nb_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index aac0129cf6..0d788aaf6c 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4665,6 +4665,8 @@ def apply(self, person_id, squeeze_factor): # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, # equipment used by surgeon, gloves and facemask + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From fdd3ed058534cee81ce95d6b6abdb0c58e43d87b Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 12:32:56 +0100 Subject: [PATCH 031/443] co: comments about consumable items --- src/tlo/methods/contraception.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 15e7dafde0..94f2c71088 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -750,6 +750,7 @@ def get_item_code_for_each_contraceptive(self): get_items_from_pkg = self.sim.modules['HealthSystem'].get_item_codes_from_package_name _cons_codes = dict() + # items for each method that requires an HSI to switch to _cons_codes['pill'] = get_items_from_pkg('Pill') _cons_codes['male_condom'] = get_items_from_pkg('Male condom') _cons_codes['other_modern'] = get_items_from_pkg('Female Condom') @@ -759,6 +760,7 @@ def get_item_code_for_each_contraceptive(self): _cons_codes['implant'] = get_items_from_pkg('Implant') _cons_codes['female_sterilization'] = get_items_from_pkg('Female sterilization') assert set(_cons_codes.keys()) == set(self.states_that_may_require_HSI_to_switch_to) + # items used when initiating a modern reliable method after not using or switching from non-reliable method _cons_codes['co_initiation'] = get_items_from_pkg('Contraception initiation') return _cons_codes From b5b13a2c744f7b00ff4d07185a1416cc19c2037e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 14:52:20 +0100 Subject: [PATCH 032/443] healthsyst: comment moved to where it belongs --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index b42fb35b01..f8bf714f0a 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2650,13 +2650,13 @@ def _reset_internal_stores(self) -> None: self._treatment_ids = defaultdict(int) # Running record of the `TREATMENT_ID`s of `HSI_Event`s self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} + # <--Same as `self._appts` but also split by facility_level # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran self._never_ran_appts = defaultdict(int) # As above, but for `HSI_Event`s that have never ran self._never_ran_appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} - # <--Same as `self._appts` but also split by facility_level self._frac_time_used_overall = [] # Running record of the usage of the healthcare system self._squeeze_factor_by_hsi_event_name = defaultdict(list) # Running record the squeeze-factor applying to each # treatment_id. Key is of the form: From a12d601eac89e749c1cfaaa40db8f327634cc3f9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:11:12 +0100 Subject: [PATCH 033/443] breast_cancer: TODOs to ask, dummy used_equipment added where Andrew requested --- src/tlo/methods/breast_cancer.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 7760340088..9b5abcf8aa 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -575,6 +575,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. + # TODO: Is it? There is 1 month as the frequency, isn't it? def apply(self, population): df = population.props # shortcut to dataframe @@ -646,6 +647,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -666,6 +668,8 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -759,6 +763,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Record used equipment + self.used_equipment = 'Anything used for mastectomy as I guess this is about' + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( From 118bb0b390ca10e38e452ad6636dd5b9cf52bb47 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:15:40 +0100 Subject: [PATCH 034/443] co: dummy used_equipment added for methods where Emi listed some --- src/tlo/methods/contraception.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 94f2c71088..67aa2902bd 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1269,7 +1269,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 record 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: @@ -1293,6 +1294,13 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Record used equipment when needed + if _new_contraceptive == 'female_sterilization': + self.used_equipment = {'Smt used to sterilize a woman'} + elif _new_contraceptive == 'IUD': + self.used_equipment = {'Equipment used when performing IUD'} + else: _new_contraceptive = "not_using" From 1868f4121f0ff15044bc180f9f37d82051acf143 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:24:02 +0100 Subject: [PATCH 035/443] healthsystem: annual equipment summary log by fac. level --- src/tlo/methods/healthsystem.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index f8bf714f0a..a0a76a44f9 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,6 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + self.used_equipment = set() @property def bed_days_allocated_to_this_event(self): @@ -1731,6 +1732,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, + equipment=hsi_event.used_equipment, ) def write_to_hsi_log( @@ -1741,6 +1743,7 @@ def write_to_hsi_log( squeeze_factor: float, did_run: bool, priority: int, + equipment: set, ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1770,6 +1773,7 @@ def write_to_hsi_log( squeeze_factor=squeeze_factor, appt_footprint=event_details.appt_footprint, level=event_details.facility_level, + equipment=equipment, ) def call_and_record_never_ran_hsi_event(self, hsi_event, priority=None): @@ -2597,6 +2601,7 @@ def apply(self, population): squeeze_factor=0.0, priority=-1, did_run=True, + equipment=set() # TODO: explore more, should it be non-emtpy in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2651,6 +2656,7 @@ def _reset_internal_stores(self) -> None: self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} # <--Same as `self._appts` but also split by facility_level + self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran @@ -2667,7 +2673,8 @@ def record_hsi_event(self, hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str + level: str, + equipment: set ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2684,6 +2691,9 @@ def record_hsi_event(self, self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number + # Update used equipment by level + self._equip_by_level[level].update(equipment) + def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2746,6 +2756,14 @@ def write_to_log_and_reset_counters(self): }, ) + logger_summary.info( + key="Equipment", + description="Sets of used equipment for each facility level in this calendar year.", + data={ + "Equipment_By_Level": self._equip_by_level, + }, + ) + self._reset_internal_stores() From 802c7d0020bfd4946d9b2841d5fd828d6c6b7bf3 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:35:32 +0100 Subject: [PATCH 036/443] long_run_all_dis: 2K pop, 1 run -- to see results fast --- .../calibration_analyses/scenarios/long_run_all_diseases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py index 8df04fc12e..756af3da53 100644 --- a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py +++ b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py @@ -22,9 +22,9 @@ def __init__(self): self.seed = 0 self.start_date = Date(2010, 1, 1) self.end_date = Date(2030, 1, 1) - self.pop_size = 20_000 + self.pop_size = 2_000 self.number_of_draws = 1 - self.runs_per_draw = 10 + self.runs_per_draw = 1 def log_configuration(self): return { From ac8265c39dea2683dd4a247800f5d9cb21481d96 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 16:12:18 +0100 Subject: [PATCH 037/443] Revert "long_run_all_dis: 2K pop, 1 run -- to see results fast" This reverts commit 6d054237a0d44cd0e57c4e46ba21e1332a9c9777. --- .../calibration_analyses/scenarios/long_run_all_diseases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py index 756af3da53..8df04fc12e 100644 --- a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py +++ b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py @@ -22,9 +22,9 @@ def __init__(self): self.seed = 0 self.start_date = Date(2010, 1, 1) self.end_date = Date(2030, 1, 1) - self.pop_size = 2_000 + self.pop_size = 20_000 self.number_of_draws = 1 - self.runs_per_draw = 1 + self.runs_per_draw = 10 def log_configuration(self): return { From 348f32998bcc35785bee86b5c4a411020e71a0ea Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 16:15:18 +0100 Subject: [PATCH 038/443] breast_cancer: mastectomy dummy equipment fixed --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 9b5abcf8aa..d49bda71df 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -764,7 +764,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] # Record used equipment - self.used_equipment = 'Anything used for mastectomy as I guess this is about' + self.used_equipment = {'Anything used for mastectomy as I guess this is about'} # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( From 5296994d97f004ed86ce274a260f0d9b0b389711 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Sep 2023 21:01:35 +0100 Subject: [PATCH 039/443] equipment_catalogue & utils: new script + a change in utils.py - to create equip. catalogue --- .../equipment/equipment_catalogue.py | 101 ++++++++++++++++++ src/tlo/analysis/utils.py | 8 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/scripts/healthsystem/equipment/equipment_catalogue.py diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py new file mode 100644 index 0000000000..c88a469c2f --- /dev/null +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -0,0 +1,101 @@ +import argparse +import pandas as pd +from pathlib import Path +from tlo.analysis.utils import extract_results +from functools import reduce + + +def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: + """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated + year.""" + + def get_equipment_declaration_by_levels(_df): + """Get the equipment declaration by facility levels for the year.""" + + def unpack_dict_in_series(_raw: pd.Series): + # Create an empty DataFrame to store the data + df = pd.DataFrame() + + # Iterate through the dictionary items + for col_name, mydict in _raw.items(): + for date, inner_dict in mydict.items(): + # Convert the inner_dict to a list of dictionaries with 'date' + data = [{'date': date, 'fac_level': inner_dict_key, 'value': inner_dict_set} for + inner_dict_key, inner_dict_set in inner_dict.items()] + # Create a DataFrame from the list with date & fac_level as indexes + temp_df = pd.DataFrame(data) + temp_df.set_index(['date', 'fac_level'], inplace=True) + temp_df.columns = [None] + + # Concatenate the temporary DataFrame to the result DataFrame + df = pd.concat([df, temp_df]) + + # print(f"\ndf\n {df}") + df.columns = [None] + + return df + + return _df \ + .set_index('date') \ + .pipe(unpack_dict_in_series) \ + .stack() \ + .droplevel(level=2) + + return extract_results( + results_folder, + module='tlo.methods.healthsystem.summary', + key='Equipment', + custom_generate_series=get_equipment_declaration_by_levels + ) + + +def create_equipment_catalogue(results_folder: Path, output_folder: Path): + # Declare path for output file from this script + output_file_name = 'equipment_catalogue_by_level.csv' + output_detailed_file_name = 'equipment_catalogue_by_date_level_sim.csv' + + sim_equipment = get_annual_equipment_declarations_by_levels(results_folder) + sim_equipment_df = pd.DataFrame(sim_equipment) + sim_equipment_df.index.names = ['date', 'fac_level'] + + # Save the detailed CSV + sim_equipment_df.to_csv(output_folder / output_detailed_file_name) + print('equipment_catalogue_by_date_level_sim.csv saved.') + + # Prepare a catalogue only by facility levels + # Define a custom aggregation function to combine sets in columns for each row + def combine_sets(row): + combined_set = set() + for col in row: + combined_set.update(col) + return combined_set + + # Apply the custom aggregation function to each row + sim_equipment_by_level_df = sim_equipment_df.copy() + sim_equipment_by_level_df['equipment'] = sim_equipment_by_level_df.apply(combine_sets, axis=1) + # Group by 'fac_level' and join rows with the same 'fac_level' into one set + sim_equipment_by_level_df.reset_index(inplace=True) + sim_equipment_by_level_df = sim_equipment_by_level_df.groupby('fac_level')['equipment'].apply( + lambda x: list(set.union(*x)) + ).reset_index() + + # Explode the 'equipment' column to separate elements into rows + sim_equipment_by_level_df = sim_equipment_by_level_df.explode('equipment', ignore_index=True).set_index('fac_level') + + # Save the CSV equipment catalogue + sim_equipment_by_level_df.to_csv(output_folder / output_file_name) + print('equipment_catalogue_by_level.csv saved.') + + return 0 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("results_folder", type=Path) + args = parser.parse_args() + + create_equipment_catalogue( + results_folder=args.results_folder, + output_folder=args.results_folder, + ) +# NB. Edit run configuration, the Parameters: "./outputs/sejjej5@ucl.ac.uk/long_run_all_diseases-2023-09-04T233551Z" diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index a9e455a455..a8e7471233 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -282,6 +282,9 @@ def get_multiplier(_draw, _run): # get number of draws and numbers of runs info = get_scenario_info(results_folder) + def is_number(element): + return isinstance(element, (int, float)) + # Collect results from each draw/run res = dict() for draw in range(info['number_of_draws']): @@ -293,7 +296,10 @@ def get_multiplier(_draw, _run): df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key] output_from_eval: pd.Series = _gen_series(df) assert pd.Series == type(output_from_eval), 'Custom command does not generate a pd.Series' - res[draw_run] = output_from_eval * get_multiplier(draw, run) + if output_from_eval.apply(is_number).all(): + res[draw_run] = output_from_eval * get_multiplier(draw, run) + else: + res[draw_run] = output_from_eval except KeyError: # Some logs could not be found - probably because this run failed. From 4552c055f8b7dd891d7a7cfc66e35b53bdf1439a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 7 Sep 2023 17:12:45 +0100 Subject: [PATCH 040/443] equipment_catalogue: PEP8 --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index c88a469c2f..fdaca54515 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -1,8 +1,9 @@ import argparse -import pandas as pd from pathlib import Path + +import pandas as pd + from tlo.analysis.utils import extract_results -from functools import reduce def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: From 993b71eb75f02e908c6143a0760e8aa98cde7ed9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova <48157464+EvaJanouskova@users.noreply.github.com> Date: Tue, 12 Sep 2023 19:53:10 +0200 Subject: [PATCH 041/443] healthsystem: sort equipment for log Co-authored-by: Tim Hallett <39991060+tbhallett@users.noreply.github.com> --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index a0a76a44f9..b06fc1f52b 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2760,7 +2760,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": self._equip_by_level, + "Equipment_By_Level": sorted(self._equip_by_level), }, ) From f81ecf623f70a620e975c4a0e0303cc7d1b2495d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 18:26:35 +0100 Subject: [PATCH 042/443] equipment_catalogue: comment updated --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index fdaca54515..ecbc206aba 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -8,7 +8,8 @@ def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated - year.""" + year. + NB. healthsystem.summary logger required to have been set at the level INFO or higher.""" def get_equipment_declaration_by_levels(_df): """Get the equipment declaration by facility levels for the year.""" From 8790071a32551c205909ba4b944bf86df772a793 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:10:01 +0100 Subject: [PATCH 043/443] rti: unified use of consumables/equipment terms --- src/tlo/methods/rti.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 0d788aaf6c..f92d751e14 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4664,9 +4664,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe @@ -5002,7 +5000,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From 7d2e1daed69f72407d0406b5df300c48d973c308 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:15:44 +0100 Subject: [PATCH 044/443] hs, brc, co: used_equipment renamed to EQUIPMENT; if equip always same for HSI - set in __init__, otherwise updated in apply --- src/tlo/methods/breast_cancer.py | 9 +++++---- src/tlo/methods/contraception.py | 7 ++++--- src/tlo/methods/healthsystem.py | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index d49bda71df..5bb370816c 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -648,6 +648,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -668,8 +670,6 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -763,8 +763,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Record used equipment - self.used_equipment = {'Anything used for mastectomy as I guess this is about'} + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 67aa2902bd..66e50a56cb 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1295,11 +1295,12 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Record used equipment when needed + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': - self.used_equipment = {'Smt used to sterilize a woman'} + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': - self.used_equipment = {'Equipment used when performing IUD'} + self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: _new_contraceptive = "not_using" diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index b06fc1f52b..b5d5721543 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,7 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - self.used_equipment = set() + self.EQUIPMENT = set() @property def bed_days_allocated_to_this_event(self): @@ -1732,7 +1732,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.used_equipment, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( From be0cc2ae26f352c48d008c1932d08a32159ddff1 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:04 +0100 Subject: [PATCH 045/443] Revert "utils: typo" This reverts commit e55a396136f0f01155ad2d0c2ccc7676a2aea99b. --- src/tlo/analysis/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index a8e7471233..6f5e5bf288 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -265,7 +265,7 @@ def get_multiplier(_draw, _run): )['tlo.methods.population']['scaling_factor']['scaling_factor'].values[0] if custom_generate_series is None: - # If there is no `custom_generate_series` provided, it implies that function required selects the specified + # If there is no `custom_generate_series` provided, it implies that function required selects a the specified # column from the dataframe. assert column is not None, "Must specify which column to extract" From 81d23d538d0cbe6e19d571ffef9e7dd3d5cdbfea Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:08 +0100 Subject: [PATCH 046/443] Revert "healthsystem: typo" This reverts commit 1bd9a6912e0dd1027782c785ad1e945f09f9b3a1. --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index b5d5721543..170cb9b921 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -354,7 +354,7 @@ def initialise(self): # If there are bed-days specified, add (if needed) the in-patient admission and in-patient day Appointment # Types. # (HSI that require a bed for one or more days always need such appointments, but this may have been - # missed in the declaration of the `EXPECTED_APPT_FOOTPRINT` in the HSI.) + # missed in the declaration of the `EXPECTED_APPPT_FOOTPRINT` in the HSI.) # NB. The in-patient day Appointment time is automatically applied on subsequent days. if sum(self.BEDDAYS_FOOTPRINT.values()): self.EXPECTED_APPT_FOOTPRINT = health_system.bed_days.add_first_day_inpatient_appts_to_footprint( From 0d0e50685350a6ad6062aaeb9674c1557a91f08c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:15 +0100 Subject: [PATCH 047/443] Revert "co: comments about consumable items" This reverts commit 950eb7aabb9c1b1dd21958ce2590a66240060bdf. --- src/tlo/methods/contraception.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 66e50a56cb..43338578fb 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -750,7 +750,6 @@ def get_item_code_for_each_contraceptive(self): get_items_from_pkg = self.sim.modules['HealthSystem'].get_item_codes_from_package_name _cons_codes = dict() - # items for each method that requires an HSI to switch to _cons_codes['pill'] = get_items_from_pkg('Pill') _cons_codes['male_condom'] = get_items_from_pkg('Male condom') _cons_codes['other_modern'] = get_items_from_pkg('Female Condom') @@ -760,7 +759,6 @@ def get_item_code_for_each_contraceptive(self): _cons_codes['implant'] = get_items_from_pkg('Implant') _cons_codes['female_sterilization'] = get_items_from_pkg('Female sterilization') assert set(_cons_codes.keys()) == set(self.states_that_may_require_HSI_to_switch_to) - # items used when initiating a modern reliable method after not using or switching from non-reliable method _cons_codes['co_initiation'] = get_items_from_pkg('Contraception initiation') return _cons_codes From 19e441bb77bafe6efc1350728f09ec7c33b81e45 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:59:27 +0100 Subject: [PATCH 048/443] Revert "healthsyst: comment moved to where it belongs" This reverts commit fbfada1c --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 170cb9b921..1dd3f22230 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2655,7 +2655,6 @@ def _reset_internal_stores(self) -> None: self._treatment_ids = defaultdict(int) # Running record of the `TREATMENT_ID`s of `HSI_Event`s self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} - # <--Same as `self._appts` but also split by facility_level self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System @@ -2663,6 +2662,7 @@ def _reset_internal_stores(self) -> None: self._never_ran_appts = defaultdict(int) # As above, but for `HSI_Event`s that have never ran self._never_ran_appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} + # <--Same as `self._appts` but also split by facility_level self._frac_time_used_overall = [] # Running record of the usage of the healthcare system self._squeeze_factor_by_hsi_event_name = defaultdict(list) # Running record the squeeze-factor applying to each # treatment_id. Key is of the form: From e0dc52be1cd4fe6465cb8f6494799d66e6bcf24a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 18 Sep 2023 17:33:16 +0100 Subject: [PATCH 049/443] brc: comments updated --- src/tlo/methods/breast_cancer.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 5bb370816c..aef476c870 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -574,8 +574,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) - # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. - # TODO: Is it? There is 1 month as the frequency, isn't it? + # scheduled to run every month: do not change as this is hard-wired into the values of all the parameters. def apply(self, population): df = population.props # shortcut to dataframe @@ -646,10 +645,9 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. - # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 4eaac38c38937f32e52674bb5f198f7a9d73a7e1 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:44:03 +0100 Subject: [PATCH 050/443] brc & co: rm the dummy examples of equipment from modules --- src/tlo/methods/breast_cancer.py | 6 ------ src/tlo/methods/contraception.py | 10 +--------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index aef476c870..1ce9ad2bf6 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,8 +646,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -761,10 +759,6 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Update equipment used with treatment - # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher - self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) - # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 43338578fb..43881a77df 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1267,8 +1267,7 @@ 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 and record used equipment if any, if not set the contraception to - # "not_using": + # if so do log the availability of all items, 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: @@ -1293,13 +1292,6 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Update equipment when needed - # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher - if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) - elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Equipment used when performing IUD'}) - else: _new_contraceptive = "not_using" From 704599c322a7f3c8e9c36036722548026dd5ca80 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:54:51 +0100 Subject: [PATCH 051/443] brc: changes in comments moved to #1105 --- src/tlo/methods/breast_cancer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1ce9ad2bf6..7760340088 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -574,7 +574,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) - # scheduled to run every month: do not change as this is hard-wired into the values of all the parameters. + # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. def apply(self, population): df = population.props # shortcut to dataframe @@ -645,7 +645,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 5613225ff27cfbee1ebfac867aefde3d00597090 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 18:06:37 +0100 Subject: [PATCH 052/443] [no ci] co: rm empty line (unintentionally added) --- src/tlo/methods/contraception.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 43881a77df..15e7dafde0 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1291,7 +1291,6 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive - else: _new_contraceptive = "not_using" From 506e5f0977f17cfcb40607e16d3a5e8529f01393 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 24 Sep 2023 16:15:42 +0100 Subject: [PATCH 053/443] RF_Equipment: equipment catalogue - first draft (from Sakshi) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv new file mode 100644 index 0000000000..b119a1d503 --- /dev/null +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17a3a35a9c7908c9a291b8906ae1f8f7be6ff89459a183900de142ee29368c53 +size 36731 From a2339e1de4789a721e191ac93bc37f2cb6a78429 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 24 Sep 2023 23:54:02 +0100 Subject: [PATCH 054/443] RF_Equipment: equipment catalogue - merge duplicates (round 1) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index b119a1d503..4e936f4cb9 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17a3a35a9c7908c9a291b8906ae1f8f7be6ff89459a183900de142ee29368c53 -size 36731 +oid sha256:faf0337415b4ef87fc47dace921c9f9fb4450d88ff1718def0a96e80fa073cb6 +size 34520 From ebe0b6a39ed1d882a8b1cdc649ef709d04bcb3f8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 09:56:16 +0100 Subject: [PATCH 055/443] RF_Equipment: equipment catalogue - merge duplicates (round 2) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 4e936f4cb9..22f62d5a0f 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:faf0337415b4ef87fc47dace921c9f9fb4450d88ff1718def0a96e80fa073cb6 -size 34520 +oid sha256:b4f78c47032a3e658fc1d07fd631c2e8aea85b145a7978fcd5d965bf9e0c5fce +size 32543 From 335ed07d2b9bed20332949a2c8d2d81169b193d1 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 00:00:00 +0100 Subject: [PATCH 056/443] brc: dummy examples of eqip added diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 776034008..365a54ea8 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -645,7 +645,9 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -759,6 +761,10 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy'}) + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( --- src/tlo/methods/breast_cancer.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 7760340088..365a54ea89 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -645,7 +645,9 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -759,6 +761,10 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy'}) + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( From 17ba98fa00024f6a2b19887d0a93c8dc7c7064b3 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 19:02:17 +0100 Subject: [PATCH 057/443] co: dummy examples of equip added --- src/tlo/methods/contraception.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 15e7dafde0..ff60e20f02 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1267,7 +1267,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: @@ -1291,6 +1292,14 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher + if _new_contraceptive == 'female_sterilization': + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + elif _new_contraceptive == 'IUD': + self.EQUIPMENT.update({'Equipment used when performing IUD'}) + else: _new_contraceptive = "not_using" From 471e0a7206373905f1510e3e79b225d29f3421ca Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 19:08:35 +0100 Subject: [PATCH 058/443] RF_Equipment: equipment catalogue - merge duplicates (round 3) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 22f62d5a0f..6178c242ba 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4f78c47032a3e658fc1d07fd631c2e8aea85b145a7978fcd5d965bf9e0c5fce -size 32543 +oid sha256:9f02f434986e8070dacc50405e2b88826be18eb6de61b4e6475e7a4cad948334 +size 32455 From 0d7437ced1f786a8ffb8793938d681e7e2661733 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 21:50:20 +0100 Subject: [PATCH 059/443] hs: debugging Equipment log - rm sorted --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1dd3f22230..64a190abbc 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2760,7 +2760,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": sorted(self._equip_by_level), + "Equipment_By_Level": self._equip_by_level, }, ) From 1dc3d25a52352de1a86b577cd6035de853e614e1 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 28 Sep 2023 00:32:23 +0100 Subject: [PATCH 060/443] RF_Equipment: equipment catalogue - merge duplicates (round 4) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 6178c242ba..aca3040b03 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f02f434986e8070dacc50405e2b88826be18eb6de61b4e6475e7a4cad948334 -size 32455 +oid sha256:29782fdacd7b13efef9b0b9de23fb7413b329bb9880b87c3b0522e6732130bc1 +size 31872 From 6ad834a38ab6ef6420a61326132e601a47982a24 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 28 Sep 2023 00:49:34 +0100 Subject: [PATCH 061/443] hs: fix sorting of _equip_by_level --- src/tlo/methods/healthsystem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 64a190abbc..c605e63e3d 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2756,6 +2756,9 @@ def write_to_log_and_reset_counters(self): }, ) + # Sort equipment within levels, and log them + for key in self._equip_by_level: + self._equip_by_level[key] = sorted(self._equip_by_level[key]) logger_summary.info( key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", From c486fea133867dbb8a64b439cc3be169471ce30d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 29 Sep 2023 10:45:25 +0100 Subject: [PATCH 062/443] RF_Equipment: equipment catalogue - merge duplicates (round 5) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index aca3040b03..0055cd8d8e 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29782fdacd7b13efef9b0b9de23fb7413b329bb9880b87c3b0522e6732130bc1 -size 31872 +oid sha256:f3854096278164e1e3c63c2aa2e7afecbae1c6ee14dabcff829af78c271cd82d +size 31712 From 026d65d808d432a739b5c51fabc5dba6febeb822 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 12:41:29 +0100 Subject: [PATCH 063/443] adding equipment for routine ANC --- .../methods/care_of_women_during_pregnancy.py | 60 +++++++++++++++---- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 71c242cee0..f02740cf4e 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -728,6 +728,7 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # We use a temporary variable to store if proteinuria is detected proteinuria_diagnosed = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'dipstick'}) + hsi_event.EQUIPMENT.update({'Urine dip Stick'}) # The process is repeated for blood pressure monitoring if self.rng.random_sample() < params['prob_intervention_delivered_bp']: @@ -736,6 +737,7 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): hsi_event=hsi_event): hypertension_diagnosed = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'bp_measurement'}) + hsi_event.EQUIPMENT.update({'Sphygmomanometer'}) if not df.at[person_id, 'ac_gest_htn_on_treatment'] and\ (df.at[person_id, 'ps_htn_disorders'] != 'none') and pd.isnull(mni[person_id]['hypertension' @@ -901,6 +903,7 @@ def point_of_care_hb_testing(self, hsi_event): logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'hb_screen'}) hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_equipment']) + hsi_event.EQUIPMENT.update({'Haemoglobinometer'}) # We run the test through the dx_manager and if a woman has anaemia and its detected she will be admitted # for further care @@ -1054,20 +1057,25 @@ 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( + if avail: + + # TODO: this actually might be a fasting blood glucose test (not using glucometer) + hsi_event.EQUIPMENT.update({'Glucometer'}) + + if self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run='blood_test_glucose', hsi_event=hsi_event): - logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'gdm_screen'}) - mni[person_id]['anc_ints'].append('gdm_screen') + 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 @@ -1454,6 +1462,11 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + # Update equipment used during first ANC visit not directly related to interventions + self.EQUIPMENT.update( + {'Weighing scale', 'Height Pole (Stadiometer)', 'MUAC tape', 'Measuring tapes', + '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) @@ -1472,6 +1485,8 @@ 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.EQUIPMENT.update({'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) @@ -1536,7 +1551,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 + # Update equipment used during ANC visit not directly related to interventions + self.EQUIPMENT.update( + {'Weighing scale', 'Measuring tapes', + '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) @@ -1620,6 +1640,8 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1692,6 +1714,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1760,6 +1785,10 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update( + {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' + '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) @@ -1827,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.EQUIPMENT.update({'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: @@ -1886,6 +1918,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1939,6 +1974,9 @@ 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.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) + self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) if df.at[person_id, 'ac_to_be_admitted']: From ded521ace976be155b989374e411409c991a0bbd Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 13:19:33 +0100 Subject: [PATCH 064/443] adding equipment for routine ANC - ultrasound --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index f02740cf4e..24f7656e3c 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1553,7 +1553,7 @@ def apply(self, person_id, squeeze_factor): # =================================== INTERVENTIONS ==================================================== # Update equipment used during ANC visit not directly related to interventions self.EQUIPMENT.update( - {'Weighing scale', 'Measuring tapes', + {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' '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 From 8db77f12fb52bbea002b0571e89e86302785e755 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 14:47:34 +0100 Subject: [PATCH 065/443] adding equipment for emergency ANC, PAC and ectopic pregnancy --- src/tlo/methods/care_of_women_during_pregnancy.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 24f7656e3c..dcde58004e 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1198,6 +1198,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.EQUIPMENT.update({'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) @@ -1238,6 +1239,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.EQUIPMENT.update({'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(): @@ -1282,6 +1285,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.EQUIPMENT.update({'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 @@ -1319,6 +1323,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.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) def antibiotics_for_prom(self, individual_id, hsi_event): """ @@ -1340,6 +1345,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.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) def ectopic_pregnancy_treatment_doesnt_run(self, hsi_event): """ @@ -2012,6 +2018,9 @@ def __init__(self, module, person_id, visit_number): self.ACCEPTED_FACILITY_LEVEL = '1a' def apply(self, person_id, squeeze_factor): + + # TODO: n.b. equipment not added for this HSI but i think it will be deleted with the next PR + df = self.sim.population.props mother = df.loc[person_id] mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -2593,6 +2602,9 @@ def apply(self, person_id, squeeze_factor): self.module, self, self.module.item_codes_preg_consumables, core='post_abortion_care_core', optional='post_abortion_care_optional') + # TODO: equipment set for dilation and cutterage? oxygen? + self.EQUIPMENT.update({'Manual Vacuum aspiration Set', 'Drip stand', 'Infusion pump'}) + # Check HCW availability to deliver surgical removal of retained products sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], sf='retained_prod', @@ -2684,6 +2696,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.EQUIPMENT.update({'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 From 64eefdffe4f87b39b1e2e6fa222708ad7c749a13 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 14:57:07 +0100 Subject: [PATCH 066/443] linting --- src/tlo/methods/care_of_women_during_pregnancy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index dcde58004e..3333500f5b 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1646,7 +1646,8 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) 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) From bccc30dd715100653e0388c916367421b54ba630 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 27 Aug 2023 23:38:26 +0100 Subject: [PATCH 067/443] utils: typo --- src/tlo/analysis/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index 089230f581..3973496e0b 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -265,7 +265,7 @@ def get_multiplier(_draw, _run): )['tlo.methods.population']['scaling_factor']['scaling_factor'].values[0] if custom_generate_series is None: - # If there is no `custom_generate_series` provided, it implies that function required selects a the specified + # If there is no `custom_generate_series` provided, it implies that function required selects the specified # column from the dataframe. assert column is not None, "Must specify which column to extract" From cff0703ad4955285e01b1f1b2d982579563cf261 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 30 Aug 2023 23:39:44 +0100 Subject: [PATCH 068/443] healthsystem: typo --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1fd0007cc7..73d7c7671f 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -353,7 +353,7 @@ def initialise(self): # If there are bed-days specified, add (if needed) the in-patient admission and in-patient day Appointment # Types. # (HSI that require a bed for one or more days always need such appointments, but this may have been - # missed in the declaration of the `EXPECTED_APPPT_FOOTPRINT` in the HSI.) + # missed in the declaration of the `EXPECTED_APPT_FOOTPRINT` in the HSI.) # NB. The in-patient day Appointment time is automatically applied on subsequent days. if sum(self.BEDDAYS_FOOTPRINT.values()): self.EXPECTED_APPT_FOOTPRINT = health_system.bed_days.add_first_day_inpatient_appts_to_footprint( From 582fc127b7fa413d3ab58bd306c9beb4ca37cd3a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 12:11:31 +0100 Subject: [PATCH 069/443] TODO: some modules use equipment when talking about consumables --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 ++ src/tlo/methods/labour.py | 2 ++ src/tlo/methods/newborn_outcomes.py | 2 ++ src/tlo/methods/rti.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index b4bc8defbb..31923c5013 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -216,6 +216,8 @@ def get_and_store_pregnancy_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_preg_consumables['blood_test_equipment'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 38b81a032c..87fc9384c3 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -692,6 +692,8 @@ def get_and_store_labour_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_lab_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 36104cf2ee..1905ac1d9f 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -410,6 +410,8 @@ def get_and_store_newborn_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_nb_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index aac0129cf6..0d788aaf6c 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4665,6 +4665,8 @@ def apply(self, person_id, squeeze_factor): # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, # equipment used by surgeon, gloves and facemask + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From 681a2982f3cb53de526a01f2f4a7ba78dbc38b41 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 12:32:56 +0100 Subject: [PATCH 070/443] co: comments about consumable items --- src/tlo/methods/contraception.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 15e7dafde0..94f2c71088 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -750,6 +750,7 @@ def get_item_code_for_each_contraceptive(self): get_items_from_pkg = self.sim.modules['HealthSystem'].get_item_codes_from_package_name _cons_codes = dict() + # items for each method that requires an HSI to switch to _cons_codes['pill'] = get_items_from_pkg('Pill') _cons_codes['male_condom'] = get_items_from_pkg('Male condom') _cons_codes['other_modern'] = get_items_from_pkg('Female Condom') @@ -759,6 +760,7 @@ def get_item_code_for_each_contraceptive(self): _cons_codes['implant'] = get_items_from_pkg('Implant') _cons_codes['female_sterilization'] = get_items_from_pkg('Female sterilization') assert set(_cons_codes.keys()) == set(self.states_that_may_require_HSI_to_switch_to) + # items used when initiating a modern reliable method after not using or switching from non-reliable method _cons_codes['co_initiation'] = get_items_from_pkg('Contraception initiation') return _cons_codes From 102c262f3d054d587cca8f3308a25afaffdb7a0b Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 14:52:20 +0100 Subject: [PATCH 071/443] healthsyst: comment moved to where it belongs --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 73d7c7671f..813c00e837 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2663,13 +2663,13 @@ def _reset_internal_stores(self) -> None: self._treatment_ids = defaultdict(int) # Running record of the `TREATMENT_ID`s of `HSI_Event`s self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} + # <--Same as `self._appts` but also split by facility_level # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran self._never_ran_appts = defaultdict(int) # As above, but for `HSI_Event`s that have never ran self._never_ran_appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} - # <--Same as `self._appts` but also split by facility_level self._frac_time_used_overall = [] # Running record of the usage of the healthcare system self._squeeze_factor_by_hsi_event_name = defaultdict(list) # Running record the squeeze-factor applying to each # treatment_id. Key is of the form: From 793fbcb24a349a559ccb4cfad7aa369fda479cd7 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:11:12 +0100 Subject: [PATCH 072/443] breast_cancer: TODOs to ask, dummy used_equipment added where Andrew requested --- src/tlo/methods/breast_cancer.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 7760340088..9b5abcf8aa 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -575,6 +575,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. + # TODO: Is it? There is 1 month as the frequency, isn't it? def apply(self, population): df = population.props # shortcut to dataframe @@ -646,6 +647,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -666,6 +668,8 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -759,6 +763,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Record used equipment + self.used_equipment = 'Anything used for mastectomy as I guess this is about' + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( From cc22e7b38f76cb1c33e19a0b0b6b1def64c81a00 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:15:40 +0100 Subject: [PATCH 073/443] co: dummy used_equipment added for methods where Emi listed some --- src/tlo/methods/contraception.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 94f2c71088..67aa2902bd 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1269,7 +1269,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 record 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: @@ -1293,6 +1294,13 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Record used equipment when needed + if _new_contraceptive == 'female_sterilization': + self.used_equipment = {'Smt used to sterilize a woman'} + elif _new_contraceptive == 'IUD': + self.used_equipment = {'Equipment used when performing IUD'} + else: _new_contraceptive = "not_using" From bb90d545df08eb79ba5db4a07650b310115c272e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:24:02 +0100 Subject: [PATCH 074/443] healthsystem: annual equipment summary log by fac. level --- src/tlo/methods/healthsystem.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 813c00e837..667abdfa63 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,6 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + self.used_equipment = set() @property def bed_days_allocated_to_this_event(self): @@ -1744,6 +1745,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, + equipment=hsi_event.used_equipment, ) def write_to_hsi_log( @@ -1754,6 +1756,7 @@ def write_to_hsi_log( squeeze_factor: float, did_run: bool, priority: int, + equipment: set, ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1783,6 +1786,7 @@ def write_to_hsi_log( squeeze_factor=squeeze_factor, appt_footprint=event_details.appt_footprint, level=event_details.facility_level, + equipment=equipment, ) def call_and_record_never_ran_hsi_event(self, hsi_event, priority=None): @@ -2610,6 +2614,7 @@ def apply(self, population): squeeze_factor=0.0, priority=-1, did_run=True, + equipment=set() # TODO: explore more, should it be non-emtpy in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2664,6 +2669,7 @@ def _reset_internal_stores(self) -> None: self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} # <--Same as `self._appts` but also split by facility_level + self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran @@ -2680,7 +2686,8 @@ def record_hsi_event(self, hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str + level: str, + equipment: set ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2697,6 +2704,9 @@ def record_hsi_event(self, self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number + # Update used equipment by level + self._equip_by_level[level].update(equipment) + def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2759,6 +2769,14 @@ def write_to_log_and_reset_counters(self): }, ) + logger_summary.info( + key="Equipment", + description="Sets of used equipment for each facility level in this calendar year.", + data={ + "Equipment_By_Level": self._equip_by_level, + }, + ) + self._reset_internal_stores() From 35def93f34e7fdebb06e42b6777fdd87ec8ad8e6 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:35:32 +0100 Subject: [PATCH 075/443] long_run_all_dis: 2K pop, 1 run -- to see results fast --- .../calibration_analyses/scenarios/long_run_all_diseases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py index 8df04fc12e..756af3da53 100644 --- a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py +++ b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py @@ -22,9 +22,9 @@ def __init__(self): self.seed = 0 self.start_date = Date(2010, 1, 1) self.end_date = Date(2030, 1, 1) - self.pop_size = 20_000 + self.pop_size = 2_000 self.number_of_draws = 1 - self.runs_per_draw = 10 + self.runs_per_draw = 1 def log_configuration(self): return { From 20ade3ef251da0af73207e28dd6ea9c3bf8a67d0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 16:12:18 +0100 Subject: [PATCH 076/443] Revert "long_run_all_dis: 2K pop, 1 run -- to see results fast" This reverts commit 6d054237a0d44cd0e57c4e46ba21e1332a9c9777. --- .../calibration_analyses/scenarios/long_run_all_diseases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py index 756af3da53..8df04fc12e 100644 --- a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py +++ b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py @@ -22,9 +22,9 @@ def __init__(self): self.seed = 0 self.start_date = Date(2010, 1, 1) self.end_date = Date(2030, 1, 1) - self.pop_size = 2_000 + self.pop_size = 20_000 self.number_of_draws = 1 - self.runs_per_draw = 1 + self.runs_per_draw = 10 def log_configuration(self): return { From 779cccce7a2ea22809c552c8639c9a3b4156d951 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 16:15:18 +0100 Subject: [PATCH 077/443] breast_cancer: mastectomy dummy equipment fixed --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 9b5abcf8aa..d49bda71df 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -764,7 +764,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] # Record used equipment - self.used_equipment = 'Anything used for mastectomy as I guess this is about' + self.used_equipment = {'Anything used for mastectomy as I guess this is about'} # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( From 59b27ddd41b9c930218ee4f5cd90183cbf5fad81 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Sep 2023 21:01:35 +0100 Subject: [PATCH 078/443] equipment_catalogue & utils: new script + a change in utils.py - to create equip. catalogue --- .../equipment/equipment_catalogue.py | 101 ++++++++++++++++++ src/tlo/analysis/utils.py | 8 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/scripts/healthsystem/equipment/equipment_catalogue.py diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py new file mode 100644 index 0000000000..c88a469c2f --- /dev/null +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -0,0 +1,101 @@ +import argparse +import pandas as pd +from pathlib import Path +from tlo.analysis.utils import extract_results +from functools import reduce + + +def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: + """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated + year.""" + + def get_equipment_declaration_by_levels(_df): + """Get the equipment declaration by facility levels for the year.""" + + def unpack_dict_in_series(_raw: pd.Series): + # Create an empty DataFrame to store the data + df = pd.DataFrame() + + # Iterate through the dictionary items + for col_name, mydict in _raw.items(): + for date, inner_dict in mydict.items(): + # Convert the inner_dict to a list of dictionaries with 'date' + data = [{'date': date, 'fac_level': inner_dict_key, 'value': inner_dict_set} for + inner_dict_key, inner_dict_set in inner_dict.items()] + # Create a DataFrame from the list with date & fac_level as indexes + temp_df = pd.DataFrame(data) + temp_df.set_index(['date', 'fac_level'], inplace=True) + temp_df.columns = [None] + + # Concatenate the temporary DataFrame to the result DataFrame + df = pd.concat([df, temp_df]) + + # print(f"\ndf\n {df}") + df.columns = [None] + + return df + + return _df \ + .set_index('date') \ + .pipe(unpack_dict_in_series) \ + .stack() \ + .droplevel(level=2) + + return extract_results( + results_folder, + module='tlo.methods.healthsystem.summary', + key='Equipment', + custom_generate_series=get_equipment_declaration_by_levels + ) + + +def create_equipment_catalogue(results_folder: Path, output_folder: Path): + # Declare path for output file from this script + output_file_name = 'equipment_catalogue_by_level.csv' + output_detailed_file_name = 'equipment_catalogue_by_date_level_sim.csv' + + sim_equipment = get_annual_equipment_declarations_by_levels(results_folder) + sim_equipment_df = pd.DataFrame(sim_equipment) + sim_equipment_df.index.names = ['date', 'fac_level'] + + # Save the detailed CSV + sim_equipment_df.to_csv(output_folder / output_detailed_file_name) + print('equipment_catalogue_by_date_level_sim.csv saved.') + + # Prepare a catalogue only by facility levels + # Define a custom aggregation function to combine sets in columns for each row + def combine_sets(row): + combined_set = set() + for col in row: + combined_set.update(col) + return combined_set + + # Apply the custom aggregation function to each row + sim_equipment_by_level_df = sim_equipment_df.copy() + sim_equipment_by_level_df['equipment'] = sim_equipment_by_level_df.apply(combine_sets, axis=1) + # Group by 'fac_level' and join rows with the same 'fac_level' into one set + sim_equipment_by_level_df.reset_index(inplace=True) + sim_equipment_by_level_df = sim_equipment_by_level_df.groupby('fac_level')['equipment'].apply( + lambda x: list(set.union(*x)) + ).reset_index() + + # Explode the 'equipment' column to separate elements into rows + sim_equipment_by_level_df = sim_equipment_by_level_df.explode('equipment', ignore_index=True).set_index('fac_level') + + # Save the CSV equipment catalogue + sim_equipment_by_level_df.to_csv(output_folder / output_file_name) + print('equipment_catalogue_by_level.csv saved.') + + return 0 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("results_folder", type=Path) + args = parser.parse_args() + + create_equipment_catalogue( + results_folder=args.results_folder, + output_folder=args.results_folder, + ) +# NB. Edit run configuration, the Parameters: "./outputs/sejjej5@ucl.ac.uk/long_run_all_diseases-2023-09-04T233551Z" diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index 3973496e0b..8c64370216 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -282,6 +282,9 @@ def get_multiplier(_draw, _run): # get number of draws and numbers of runs info = get_scenario_info(results_folder) + def is_number(element): + return isinstance(element, (int, float)) + # Collect results from each draw/run res = dict() for draw in range(info['number_of_draws']): @@ -293,7 +296,10 @@ def get_multiplier(_draw, _run): df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key] output_from_eval: pd.Series = _gen_series(df) assert pd.Series == type(output_from_eval), 'Custom command does not generate a pd.Series' - res[draw_run] = output_from_eval * get_multiplier(draw, run) + if output_from_eval.apply(is_number).all(): + res[draw_run] = output_from_eval * get_multiplier(draw, run) + else: + res[draw_run] = output_from_eval except KeyError: # Some logs could not be found - probably because this run failed. From d8eccd7010efacfc8cdea077a6ba51dbf031fbc0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 7 Sep 2023 17:12:45 +0100 Subject: [PATCH 079/443] equipment_catalogue: PEP8 --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index c88a469c2f..fdaca54515 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -1,8 +1,9 @@ import argparse -import pandas as pd from pathlib import Path + +import pandas as pd + from tlo.analysis.utils import extract_results -from functools import reduce def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: From 9ab1de82cd91770f5a7a50d1629455adfa660325 Mon Sep 17 00:00:00 2001 From: Eva Janouskova <48157464+EvaJanouskova@users.noreply.github.com> Date: Tue, 12 Sep 2023 19:53:10 +0200 Subject: [PATCH 080/443] healthsystem: sort equipment for log Co-authored-by: Tim Hallett <39991060+tbhallett@users.noreply.github.com> --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 667abdfa63..76dd8013b4 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2773,7 +2773,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": self._equip_by_level, + "Equipment_By_Level": sorted(self._equip_by_level), }, ) From ed22fabb6b09d686f48f6889569053c854b6d2f1 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 18:26:35 +0100 Subject: [PATCH 081/443] equipment_catalogue: comment updated --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index fdaca54515..ecbc206aba 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -8,7 +8,8 @@ def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated - year.""" + year. + NB. healthsystem.summary logger required to have been set at the level INFO or higher.""" def get_equipment_declaration_by_levels(_df): """Get the equipment declaration by facility levels for the year.""" From a4081fdb023c71c9f96fc2b0f8a29812f337caac Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:10:01 +0100 Subject: [PATCH 082/443] rti: unified use of consumables/equipment terms --- src/tlo/methods/rti.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 0d788aaf6c..f92d751e14 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4664,9 +4664,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe @@ -5002,7 +5000,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From a57d3a4941638c156daa5f9ea160bd19f4c6185e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:15:44 +0100 Subject: [PATCH 083/443] hs, brc, co: used_equipment renamed to EQUIPMENT; if equip always same for HSI - set in __init__, otherwise updated in apply --- src/tlo/methods/breast_cancer.py | 9 +++++---- src/tlo/methods/contraception.py | 7 ++++--- src/tlo/methods/healthsystem.py | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index d49bda71df..5bb370816c 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -648,6 +648,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -668,8 +670,6 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -763,8 +763,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Record used equipment - self.used_equipment = {'Anything used for mastectomy as I guess this is about'} + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 67aa2902bd..66e50a56cb 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1295,11 +1295,12 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Record used equipment when needed + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': - self.used_equipment = {'Smt used to sterilize a woman'} + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': - self.used_equipment = {'Equipment used when performing IUD'} + self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: _new_contraceptive = "not_using" diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 76dd8013b4..5dfd288f30 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,7 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - self.used_equipment = set() + self.EQUIPMENT = set() @property def bed_days_allocated_to_this_event(self): @@ -1745,7 +1745,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.used_equipment, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( From a85cbb52e3b153af8afbd6ce8536fd9355d10d7c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:04 +0100 Subject: [PATCH 084/443] Revert "utils: typo" This reverts commit e55a396136f0f01155ad2d0c2ccc7676a2aea99b. --- src/tlo/analysis/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index 8c64370216..e145a2d831 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -265,7 +265,7 @@ def get_multiplier(_draw, _run): )['tlo.methods.population']['scaling_factor']['scaling_factor'].values[0] if custom_generate_series is None: - # If there is no `custom_generate_series` provided, it implies that function required selects the specified + # If there is no `custom_generate_series` provided, it implies that function required selects a the specified # column from the dataframe. assert column is not None, "Must specify which column to extract" From e9b1652bc788780d9f074454f876175e32a42a20 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:08 +0100 Subject: [PATCH 085/443] Revert "healthsystem: typo" This reverts commit 1bd9a6912e0dd1027782c785ad1e945f09f9b3a1. --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 5dfd288f30..64ef4fb065 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -354,7 +354,7 @@ def initialise(self): # If there are bed-days specified, add (if needed) the in-patient admission and in-patient day Appointment # Types. # (HSI that require a bed for one or more days always need such appointments, but this may have been - # missed in the declaration of the `EXPECTED_APPT_FOOTPRINT` in the HSI.) + # missed in the declaration of the `EXPECTED_APPPT_FOOTPRINT` in the HSI.) # NB. The in-patient day Appointment time is automatically applied on subsequent days. if sum(self.BEDDAYS_FOOTPRINT.values()): self.EXPECTED_APPT_FOOTPRINT = health_system.bed_days.add_first_day_inpatient_appts_to_footprint( From aa56a46f8c007cd53978cf01f4f57b455496a40e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:15 +0100 Subject: [PATCH 086/443] Revert "co: comments about consumable items" This reverts commit 950eb7aabb9c1b1dd21958ce2590a66240060bdf. --- src/tlo/methods/contraception.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 66e50a56cb..43338578fb 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -750,7 +750,6 @@ def get_item_code_for_each_contraceptive(self): get_items_from_pkg = self.sim.modules['HealthSystem'].get_item_codes_from_package_name _cons_codes = dict() - # items for each method that requires an HSI to switch to _cons_codes['pill'] = get_items_from_pkg('Pill') _cons_codes['male_condom'] = get_items_from_pkg('Male condom') _cons_codes['other_modern'] = get_items_from_pkg('Female Condom') @@ -760,7 +759,6 @@ def get_item_code_for_each_contraceptive(self): _cons_codes['implant'] = get_items_from_pkg('Implant') _cons_codes['female_sterilization'] = get_items_from_pkg('Female sterilization') assert set(_cons_codes.keys()) == set(self.states_that_may_require_HSI_to_switch_to) - # items used when initiating a modern reliable method after not using or switching from non-reliable method _cons_codes['co_initiation'] = get_items_from_pkg('Contraception initiation') return _cons_codes From 44a1c4b1273c02a015228da3dc83c83e90ed55ed Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:59:27 +0100 Subject: [PATCH 087/443] Revert "healthsyst: comment moved to where it belongs" This reverts commit fbfada1c --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 64ef4fb065..a3217d7109 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2668,7 +2668,6 @@ def _reset_internal_stores(self) -> None: self._treatment_ids = defaultdict(int) # Running record of the `TREATMENT_ID`s of `HSI_Event`s self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} - # <--Same as `self._appts` but also split by facility_level self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System @@ -2676,6 +2675,7 @@ def _reset_internal_stores(self) -> None: self._never_ran_appts = defaultdict(int) # As above, but for `HSI_Event`s that have never ran self._never_ran_appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} + # <--Same as `self._appts` but also split by facility_level self._frac_time_used_overall = [] # Running record of the usage of the healthcare system self._squeeze_factor_by_hsi_event_name = defaultdict(list) # Running record the squeeze-factor applying to each # treatment_id. Key is of the form: From 40051af7a50be5eb822e65227d956045ce179971 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 18 Sep 2023 17:33:16 +0100 Subject: [PATCH 088/443] brc: comments updated --- src/tlo/methods/breast_cancer.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 5bb370816c..aef476c870 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -574,8 +574,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) - # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. - # TODO: Is it? There is 1 month as the frequency, isn't it? + # scheduled to run every month: do not change as this is hard-wired into the values of all the parameters. def apply(self, population): df = population.props # shortcut to dataframe @@ -646,10 +645,9 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. - # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props From dfde4851293ab8cf992de34e6a629ea5437a1e1e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:44:03 +0100 Subject: [PATCH 089/443] brc & co: rm the dummy examples of equipment from modules --- src/tlo/methods/breast_cancer.py | 6 ------ src/tlo/methods/contraception.py | 10 +--------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index aef476c870..1ce9ad2bf6 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,8 +646,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -761,10 +759,6 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Update equipment used with treatment - # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher - self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) - # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 43338578fb..43881a77df 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1267,8 +1267,7 @@ 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 and record used equipment if any, if not set the contraception to - # "not_using": + # if so do log the availability of all items, 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: @@ -1293,13 +1292,6 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Update equipment when needed - # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher - if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) - elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Equipment used when performing IUD'}) - else: _new_contraceptive = "not_using" From 7c037ce37ffc2e8f9bd6bd5c68a9432b764ba7d5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:54:51 +0100 Subject: [PATCH 090/443] brc: changes in comments moved to #1105 --- src/tlo/methods/breast_cancer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1ce9ad2bf6..7760340088 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -574,7 +574,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) - # scheduled to run every month: do not change as this is hard-wired into the values of all the parameters. + # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. def apply(self, population): df = population.props # shortcut to dataframe @@ -645,7 +645,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 04182fc10825b6fddb4bac731ed1d56cd6305f82 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 18:06:37 +0100 Subject: [PATCH 091/443] [no ci] co: rm empty line (unintentionally added) --- src/tlo/methods/contraception.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 43881a77df..15e7dafde0 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1291,7 +1291,6 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive - else: _new_contraceptive = "not_using" From 7bcfbd1db34a8fb7870a0bbedada8b58bddc7d73 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 24 Sep 2023 16:15:42 +0100 Subject: [PATCH 092/443] RF_Equipment: equipment catalogue - first draft (from Sakshi) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv new file mode 100644 index 0000000000..b119a1d503 --- /dev/null +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17a3a35a9c7908c9a291b8906ae1f8f7be6ff89459a183900de142ee29368c53 +size 36731 From 8263b0ea9db9b6746c96eedbe09a5ff60163e4c8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 24 Sep 2023 23:54:02 +0100 Subject: [PATCH 093/443] RF_Equipment: equipment catalogue - merge duplicates (round 1) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index b119a1d503..4e936f4cb9 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17a3a35a9c7908c9a291b8906ae1f8f7be6ff89459a183900de142ee29368c53 -size 36731 +oid sha256:faf0337415b4ef87fc47dace921c9f9fb4450d88ff1718def0a96e80fa073cb6 +size 34520 From c513fa1f9607a40ad898e8ebd6d2635263a6d05a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 09:56:16 +0100 Subject: [PATCH 094/443] RF_Equipment: equipment catalogue - merge duplicates (round 2) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 4e936f4cb9..22f62d5a0f 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:faf0337415b4ef87fc47dace921c9f9fb4450d88ff1718def0a96e80fa073cb6 -size 34520 +oid sha256:b4f78c47032a3e658fc1d07fd631c2e8aea85b145a7978fcd5d965bf9e0c5fce +size 32543 From f5cf611912db72a35b6c78982e64e2d6e0c4ea59 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 21:50:20 +0100 Subject: [PATCH 095/443] hs: debugging Equipment log - rm sorted --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index a3217d7109..b0bebaf3c8 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2773,7 +2773,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": sorted(self._equip_by_level), + "Equipment_By_Level": self._equip_by_level, }, ) From f1aa86c9a3b28b5640e01e366847a318e0d03a68 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 28 Sep 2023 00:49:34 +0100 Subject: [PATCH 096/443] hs: fix sorting of _equip_by_level --- src/tlo/methods/healthsystem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index b0bebaf3c8..3a7d01387d 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2769,6 +2769,9 @@ def write_to_log_and_reset_counters(self): }, ) + # Sort equipment within levels, and log them + for key in self._equip_by_level: + self._equip_by_level[key] = sorted(self._equip_by_level[key]) logger_summary.info( key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", From 24b6341fb4cfb006a87c43a93699b5bd5e0e2b1e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:15:44 +0100 Subject: [PATCH 097/443] brc, co: used_equipment renamed to EQUIPMENT; if equip always same for HSI - set in __init__, otherwise updated in apply diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 776034008..41456eae6 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -575,6 +575,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. + # TODO: Is it? There is 1 month as the frequency, isn't it? def apply(self, population): df = population.props # shortcut to dataframe @@ -646,6 +647,9 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -759,6 +763,9 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Update equipment + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 15e7dafde..799e02f42 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1291,6 +1291,13 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) ) _new_contraceptive = self.new_contraceptive + + # Update equipment + if _new_contraceptive == 'female_sterilization': + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + elif _new_contraceptive == 'IUD': + self.EQUIPMENT.update({'Equipment used when performing IUD'}) + else: _new_contraceptive = "not_using" --- src/tlo/methods/breast_cancer.py | 7 +++++++ src/tlo/methods/contraception.py | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 7760340088..41456eae6e 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -575,6 +575,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. + # TODO: Is it? There is 1 month as the frequency, isn't it? def apply(self, population): df = population.props # shortcut to dataframe @@ -646,6 +647,9 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -759,6 +763,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Update equipment + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 15e7dafde0..799e02f420 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1291,6 +1291,13 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Update equipment + if _new_contraceptive == 'female_sterilization': + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + elif _new_contraceptive == 'IUD': + self.EQUIPMENT.update({'Equipment used when performing IUD'}) + else: _new_contraceptive = "not_using" From 18e3389f02bfbdee1d3f8e9cc26464cbbd855461 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 00:00:00 +0100 Subject: [PATCH 098/443] brc: dummy examples of eqip added; comments updated diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 776034008..365a54ea8 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -645,7 +645,9 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -759,6 +761,10 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy'}) + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 41456eae6..5dd6de770 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -646,10 +646,9 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. - # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -764,7 +763,7 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] # Update equipment - self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) + self.EQUIPMENT.update({'Anything used for mastectomy'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( --- src/tlo/methods/breast_cancer.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 41456eae6e..5dd6de770c 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,10 +646,9 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. - # TODO: what this means, should be the mammography done within this event, or the biopsy, or both? - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -764,7 +763,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] # Update equipment - self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) + self.EQUIPMENT.update({'Anything used for mastectomy'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( From eadb2a2c68f391447b9381c36102f3495ac17671 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 19:02:17 +0100 Subject: [PATCH 099/443] co: dummy examples of equip added diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 799e02f42..c2ac8af15 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1267,7 +1267,8 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) 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: --- src/tlo/methods/contraception.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 799e02f420..c2ac8af154 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1267,7 +1267,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: From 7fb7a65fb6eae976d24a89a2e8f2ce200baae338 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 19:08:35 +0100 Subject: [PATCH 100/443] RF_Equipment: equipment catalogue - merge duplicates (round 3) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 22f62d5a0f..6178c242ba 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4f78c47032a3e658fc1d07fd631c2e8aea85b145a7978fcd5d965bf9e0c5fce -size 32543 +oid sha256:9f02f434986e8070dacc50405e2b88826be18eb6de61b4e6475e7a4cad948334 +size 32455 From b03b151ecb99885d5e31860ad9dd701c880779a6 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 28 Sep 2023 00:32:23 +0100 Subject: [PATCH 101/443] RF_Equipment: equipment catalogue - merge duplicates (round 4) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 6178c242ba..aca3040b03 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f02f434986e8070dacc50405e2b88826be18eb6de61b4e6475e7a4cad948334 -size 32455 +oid sha256:29782fdacd7b13efef9b0b9de23fb7413b329bb9880b87c3b0522e6732130bc1 +size 31872 From 3a70d9528d166b9c96f50c07c09843ecf6ab4322 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 29 Sep 2023 10:45:25 +0100 Subject: [PATCH 102/443] RF_Equipment: equipment catalogue - merge duplicates (round 5) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index aca3040b03..0055cd8d8e 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29782fdacd7b13efef9b0b9de23fb7413b329bb9880b87c3b0522e6732130bc1 -size 31872 +oid sha256:f3854096278164e1e3c63c2aa2e7afecbae1c6ee14dabcff829af78c271cd82d +size 31712 From 2c202b45cf0fb2d83644f7d38fd8b2ac7768389d Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 12:41:29 +0100 Subject: [PATCH 103/443] adding equipment for routine ANC --- .../methods/care_of_women_during_pregnancy.py | 60 +++++++++++++++---- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 31923c5013..c43093e6f7 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -748,6 +748,7 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # We use a temporary variable to store if proteinuria is detected proteinuria_diagnosed = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'dipstick'}) + hsi_event.EQUIPMENT.update({'Urine dip Stick'}) # The process is repeated for blood pressure monitoring if self.rng.random_sample() < params['prob_intervention_delivered_bp']: @@ -756,6 +757,7 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): hsi_event=hsi_event): hypertension_diagnosed = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'bp_measurement'}) + hsi_event.EQUIPMENT.update({'Sphygmomanometer'}) if not df.at[person_id, 'ac_gest_htn_on_treatment'] and\ (df.at[person_id, 'ps_htn_disorders'] != 'none') and pd.isnull(mni[person_id]['hypertension' @@ -921,6 +923,7 @@ def point_of_care_hb_testing(self, hsi_event): logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'hb_screen'}) hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_equipment']) + hsi_event.EQUIPMENT.update({'Haemoglobinometer'}) # We run the test through the dx_manager and if a woman has anaemia and its detected she will be admitted # for further care @@ -1074,20 +1077,25 @@ 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( + if avail: + + # TODO: this actually might be a fasting blood glucose test (not using glucometer) + hsi_event.EQUIPMENT.update({'Glucometer'}) + + if self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run='blood_test_glucose', hsi_event=hsi_event): - logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'gdm_screen'}) - mni[person_id]['anc_ints'].append('gdm_screen') + 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 @@ -1450,6 +1458,11 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + # Update equipment used during first ANC visit not directly related to interventions + self.EQUIPMENT.update( + {'Weighing scale', 'Height Pole (Stadiometer)', 'MUAC tape', 'Measuring tapes', + '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) @@ -1468,6 +1481,8 @@ 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.EQUIPMENT.update({'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) @@ -1536,7 +1551,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 + # Update equipment used during ANC visit not directly related to interventions + self.EQUIPMENT.update( + {'Weighing scale', 'Measuring tapes', + '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) @@ -1624,6 +1644,8 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1700,6 +1722,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1772,6 +1797,10 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update( + {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' + '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) @@ -1843,6 +1872,9 @@ def apply(self, person_id, squeeze_factor): gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'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: @@ -1906,6 +1938,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1963,6 +1998,9 @@ 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.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) + self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) if df.at[person_id, 'ac_to_be_admitted']: From fb555bf3d830289f9efdf4e2b5cfb8ac9ffac91a Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 13:19:33 +0100 Subject: [PATCH 104/443] adding equipment for routine ANC - ultrasound --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index c43093e6f7..1d56575dc4 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1553,7 +1553,7 @@ def apply(self, person_id, squeeze_factor): # =================================== INTERVENTIONS ==================================================== # Update equipment used during ANC visit not directly related to interventions self.EQUIPMENT.update( - {'Weighing scale', 'Measuring tapes', + {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' '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 From 0bdf0d8be054a7ff71bd442f09082ea56f150388 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 14:47:34 +0100 Subject: [PATCH 105/443] adding equipment for emergency ANC, PAC and ectopic pregnancy --- src/tlo/methods/care_of_women_during_pregnancy.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 1d56575dc4..2e07f1f3c2 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1218,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.EQUIPMENT.update({'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) @@ -1258,6 +1259,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.EQUIPMENT.update({'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(): @@ -1302,6 +1305,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.EQUIPMENT.update({'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 @@ -1339,6 +1343,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.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) def antibiotics_for_prom(self, individual_id, hsi_event): """ @@ -1360,6 +1365,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.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) def ectopic_pregnancy_treatment_doesnt_run(self, hsi_event): """ @@ -2040,6 +2046,9 @@ def __init__(self, module, person_id, visit_number): self.ACCEPTED_FACILITY_LEVEL = '1a' def apply(self, person_id, squeeze_factor): + + # TODO: n.b. equipment not added for this HSI but i think it will be deleted with the next PR + df = self.sim.population.props mother = df.loc[person_id] mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -2602,6 +2611,9 @@ def apply(self, person_id, squeeze_factor): self.module, self, self.module.item_codes_preg_consumables, core='post_abortion_care_core', optional='post_abortion_care_optional') + # TODO: equipment set for dilation and cutterage? oxygen? + self.EQUIPMENT.update({'Manual Vacuum aspiration Set', 'Drip stand', 'Infusion pump'}) + # Check HCW availability to deliver surgical removal of retained products sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], sf='retained_prod', @@ -2687,6 +2699,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.EQUIPMENT.update({'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 From 81095ddb809c4c03bd47fccce3289cbe4f0c497e Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 14:57:07 +0100 Subject: [PATCH 106/443] linting --- src/tlo/methods/care_of_women_during_pregnancy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 2e07f1f3c2..73a7c1cc31 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1650,7 +1650,8 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) 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) From d0ea570660d7773b2f32a7bd3a110a34f80ed2cb Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 10:22:01 +0100 Subject: [PATCH 107/443] added some comments on equipment I think needed --- src/tlo/methods/breast_cancer.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 5dd6de770c..e54ab60ecb 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -648,6 +648,12 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + + # ap_oct23 - Eva, I cannot locate a biopsy needle in the equipment - perhaps it is in consumables + # the other equipment need is for histology in the lab - there is a whole long list of items needed + # for histology done on the sample in the lab - do we need to add each of these, or can we have a + # package ? I do not think mammography is done at this point but I could be wrong. + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): @@ -727,6 +733,9 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + # ap_oct23 - I believe this will almost always be mastectomy surgery with chemotherapy, so I think for equipment + # we just need the standard surgery equipment list. We may need to add radiotherapy when more available. + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -792,6 +801,10 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # ap_oct23 - Eva, I'm not aware of any equipment needed here. Clinical guidelines do not specify what + # checks or monitoring are indicated + + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -848,6 +861,9 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # not sure there is any need for equipment here + + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From 0b1c7b7c4ca13e878a2222c9f14c9ca285d115c4 Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 10:34:54 +0100 Subject: [PATCH 108/443] added some comments on equipment I think needed --- src/tlo/methods/oesophagealcancer.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index fb8e96116c..e8714176ec 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -644,6 +644,9 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # I think this will need endoscope and biopsy needle. Also lab equipment needed to perform histology. + # I can't see endoscope in equipment list but it may be given a slightly different name + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -713,6 +716,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + # equipment need here will be surgery + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -771,6 +776,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # equipment: I assume endoscope needed for this + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -827,6 +834,10 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # when radiology available then palliative radiology may be performed but suggest we don't need to include yet + # not sure what equipment needed for Endoscopic stent placement or Feeding tube which are done as palliative + # measures + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From 9500f9a9d94fb4ca94cf0cdb001c317190e8633b Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 10:52:39 +0100 Subject: [PATCH 109/443] added some comments on equipment I think needed --- src/tlo/methods/prostate_cancer.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 255c03a525..95425eba9f 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -671,6 +671,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -716,6 +718,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -761,6 +765,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -830,6 +836,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + # equipment as required for surgery + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -889,6 +897,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # possibly biopsy and histology + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -945,6 +955,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # generally not sure equipment is required as therapy is with drug, but can require castration surgery + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From e47bf3d3e31dda906bcae78bcdd3fcee8c4547ab Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:03:30 +0100 Subject: [PATCH 110/443] added some comments on equipment I think needed --- src/tlo/methods/bladder_cancer.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 7231125519..300c7d486f 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -658,6 +658,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # equipment: (ultrsound guided) biopsy, lab equipment for histology + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -721,6 +723,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # equipment: (ultrsound guided) biopsy, lab equipment for histology + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -789,6 +793,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) + # equipment: standard equipment for surgery + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -848,6 +854,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # I assume ultrasound (Ultrasound scanning machine) and biopsy + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -904,6 +912,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # no equipment as far as I am aware + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From ef03911ad9545e2936abdf5fded60e80135d691e Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:10:17 +0100 Subject: [PATCH 111/443] added some comments on equipment I think needed --- src/tlo/methods/other_adult_cancers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 508e96c12b..ba0e60d6e9 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -635,6 +635,9 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # equipment: investigations will differ by presenting symptom, but suggest we have biopsy and histology + # and ultrasound + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -705,6 +708,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + # equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -765,6 +770,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # equipment: some checks will involve further biopsy, ultrasound, histology + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -825,6 +832,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # equipment: in general not required I don't think + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From afc2a0cee79cc6e5a3e71d87c0da396af09e7bc1 Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:32:53 +0100 Subject: [PATCH 112/443] added some comments on equipment I think needed --- src/tlo/methods/epilepsy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tlo/methods/epilepsy.py b/src/tlo/methods/epilepsy.py index f06c62d098..55d6553106 100644 --- a/src/tlo/methods/epilepsy.py +++ b/src/tlo/methods/epilepsy.py @@ -591,6 +591,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # no equipment needed + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -622,6 +624,7 @@ def apply(self, person_id, squeeze_factor): tclose=None, priority=0) + # todo: may need to consider iv diazepam as another hsi class HSI_Epilepsy_Follow_Up(HSI_Event, IndividualScopeEventMixin): @@ -637,6 +640,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self._counter_of_failed_attempts_due_to_unavailable_medicines = 0 + # no equipment needed + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From f9fbdec5a7fbb549b11c2e7ef09c0356cd2b663d Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:34:06 +0100 Subject: [PATCH 113/443] added some comments on equipment I think needed --- src/tlo/methods/depression.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tlo/methods/depression.py b/src/tlo/methods/depression.py index 8f5fd9661c..dbbb90db21 100644 --- a/src/tlo/methods/depression.py +++ b/src/tlo/methods/depression.py @@ -787,6 +787,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self.num_of_sessions_had = 0 # A counter for the number of sessions of talking therapy had + # no equipment needed + def apply(self, person_id, squeeze_factor): """Set the property `de_ever_talk_ther` to be True and schedule the next session in the course if the person has not yet had 5 sessions.""" @@ -819,6 +821,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MentOPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # no equipment needed + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -860,6 +864,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + # no equipment needed + def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 2ea58cf72fc9ed36672627e9a3bd5a44ebf98a9e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 9 Oct 2023 17:22:13 +0100 Subject: [PATCH 114/443] PEP 8 --- src/tlo/methods/breast_cancer.py | 2 -- src/tlo/methods/epilepsy.py | 1 + src/tlo/methods/other_adult_cancers.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index e54ab60ecb..61ac1f42e5 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -804,7 +804,6 @@ def __init__(self, module, person_id): # ap_oct23 - Eva, I'm not aware of any equipment needed here. Clinical guidelines do not specify what # checks or monitoring are indicated - def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -863,7 +862,6 @@ def __init__(self, module, person_id): # not sure there is any need for equipment here - def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] diff --git a/src/tlo/methods/epilepsy.py b/src/tlo/methods/epilepsy.py index 55d6553106..067e53a006 100644 --- a/src/tlo/methods/epilepsy.py +++ b/src/tlo/methods/epilepsy.py @@ -626,6 +626,7 @@ def apply(self, person_id, squeeze_factor): # todo: may need to consider iv diazepam as another hsi + class HSI_Epilepsy_Follow_Up(HSI_Event, IndividualScopeEventMixin): def __init__(self, module, person_id): diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index ba0e60d6e9..1b5828af62 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -708,7 +708,7 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - # equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available + # equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available def apply(self, person_id, squeeze_factor): df = self.sim.population.props From d3b2dec291dc72ebc3744c1aa45d7236007afce8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 10 Oct 2023 10:15:28 +0100 Subject: [PATCH 115/443] co: empty equipment declaration --- src/tlo/methods/contraception.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index c2ac8af154..6e447fc9bb 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1206,6 +1206,7 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level + self.EQUIPMENT = set() # no specific equipment required unless changed in some circumstances @property def EXPECTED_APPT_FOOTPRINT(self): From 2bca6cb47512aa39a0889f0c1409b0a475f3169a Mon Sep 17 00:00:00 2001 From: Tara <37845078+tdm32@users.noreply.github.com> Date: Wed, 25 Oct 2023 12:05:14 +0100 Subject: [PATCH 116/443] Equipment/integration in modules tara (#1146) * co: rm comment * ma: add equipment for malaria HSIs * hiv: add equipment for HIV HSIs * tb: add equipment for TB HSIs * linting * make equipment declaration conditional on appt actually occurring * add comments on equipment for xray * hv, ma & tb: finalise equipment induction style --------- Co-authored-by: Eva Janouskova --- src/tlo/methods/contraception.py | 2 +- src/tlo/methods/hiv.py | 11 ++++++++++- src/tlo/methods/malaria.py | 9 +++++++++ src/tlo/methods/tb.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 6e447fc9bb..01be9f9dba 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1206,7 +1206,7 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level - self.EQUIPMENT = set() # no specific equipment required unless changed in some circumstances + self.EQUIPMENT = set() @property def EXPECTED_APPT_FOOTPRINT(self): diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index cf257cfce9..0e8d27f96b 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -2098,6 +2098,7 @@ def __init__( self.TREATMENT_ID = "Hiv_Test" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"VCTNegative": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """Do the testing and referring to other services""" @@ -2235,6 +2236,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MaleCirc": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.number_of_occurrences = 0 + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """ Do the circumcision for this man. If he is already circumcised, this is a follow-up appointment.""" @@ -2255,6 +2257,10 @@ def apply(self, person_id, squeeze_factor): # Update circumcision state df.at[person_id, "li_is_circ"] = True + # Update equipment + self.EQUIPMENT.update({'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( @@ -2291,6 +2297,7 @@ def __init__(self, module, person_id, referred_from, repeat_visits): self.ACCEPTED_FACILITY_LEVEL = '1a' self.referred_from = referred_from self.repeat_visits = repeat_visits + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """ @@ -2359,6 +2366,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Hiv_Prevention_Prep" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"PharmDispensing": 1, "VCTNegative": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """Start PrEP for this person; or continue them on PrEP for 3 more months""" @@ -2420,6 +2428,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.counter_for_drugs_not_available = 0 self.counter_for_did_not_run = 0 + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """This is a Health System Interaction Event - start or continue HIV treatment for 6 more months""" @@ -2540,7 +2549,7 @@ def do_at_initiation(self, person_id): if drugs_available: # Assign person to be have suppressed or un-suppressed viral load # (If person is VL suppressed This will prevent the Onset of AIDS, or an AIDS death if AIDS has already - # onset,) + # onset) vl_status = self.determine_vl_status( age_of_person=person["age_years"] ) diff --git a/src/tlo/methods/malaria.py b/src/tlo/methods/malaria.py index 8d1cfc17a5..96c120e710 100644 --- a/src/tlo/methods/malaria.py +++ b/src/tlo/methods/malaria.py @@ -751,6 +751,7 @@ def __init__(self, module, person_id, facility_level='1a'): 'Under5OPD' if person_age_years < 5 else 'Over5OPD': 1} ) self.ACCEPTED_FACILITY_LEVEL = '1a' if (self.facility_level == '1a') else '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -848,6 +849,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Malaria_Test' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ConWithDCSA': 1}) self.ACCEPTED_FACILITY_LEVEL = '0' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -900,6 +902,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -992,6 +995,7 @@ def __init__(self, module, person_id): ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -1015,6 +1019,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 + # Update equipment + self.EQUIPMENT.update({'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) @@ -1045,6 +1053,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Malaria_Prevention_Iptp' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 79afd6fa5f..4b61b67526 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -1721,6 +1721,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Screening" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """Do the screening and referring to next tests""" @@ -1814,6 +1815,9 @@ def apply(self, person_id, squeeze_factor): test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_clinical", hsi_event=self ) + if test_result is not None: + # Update equipment + self.EQUIPMENT.update({'Sputum Collection box', 'Ordinary Microscope'}) elif test == "xpert": ACTUAL_APPT_FOOTPRINT = self.make_appt_footprint( @@ -1829,12 +1833,16 @@ def apply(self, person_id, squeeze_factor): test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_xpert_test_smear_negative", hsi_event=self ) + if test_result is not None: + # Update equipment + self.EQUIPMENT.update({'Sputum Collection box', 'Gene Expert (16 Module)'}) # ------------------------- testing referrals ------------------------- # # if none of the tests are available, try again for sputum # requires another appointment - added in ACTUAL_APPT_FOOTPRINT if test_result is None: + if smear_status: test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_sputum_test_smear_positive", hsi_event=self @@ -1847,9 +1855,13 @@ def apply(self, person_id, squeeze_factor): ACTUAL_APPT_FOOTPRINT = self.make_appt_footprint( {"Over5OPD": 2, "LabTBMicro": 1} ) + if test_result is not None: + # Update equipment + self.EQUIPMENT.update({'Sputum Collection box', 'Ordinary Microscope'}) # if still no result available, rely on clinical diagnosis if test_result is None: + test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_clinical", hsi_event=self ) @@ -1956,6 +1968,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Clinical" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Under5OPD": 0.5}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """ Do the screening and referring process """ @@ -2023,6 +2036,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Xray" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"DiagRadio": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -2044,6 +2058,9 @@ 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: + # Update equipment + self.EQUIPMENT.update({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items # if consumables not available, refer to level 2 # return blank footprint as xray did not occur @@ -2094,6 +2111,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Xray" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"DiagRadio": 1}) self.ACCEPTED_FACILITY_LEVEL = '2' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -2115,6 +2133,9 @@ 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: + # Update equipment + self.EQUIPMENT.update({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items # if consumables not available, rely on clinical diagnosis # return blank footprint as xray was not available @@ -2162,6 +2183,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Treatment" self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() self.number_of_occurrences = 0 @property @@ -2310,6 +2332,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Test_FollowUp" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"TBFollowUp": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): p = self.module.parameters @@ -2373,6 +2396,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: + # Update equipment + self.EQUIPMENT.update({'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"]: @@ -2387,6 +2413,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: + # Update equipment + self.EQUIPMENT.update({'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"): @@ -2441,6 +2470,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Prevention_Ipt" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() self.number_of_occurrences = 0 def apply(self, person_id, squeeze_factor): From 8f935a4930a79c2c47ec40da0a94a239f066976a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 25 Oct 2023 12:09:41 +0100 Subject: [PATCH 117/443] tb: no additional lines --- src/tlo/methods/tb.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 4b61b67526..691412dc96 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -1842,7 +1842,6 @@ def apply(self, person_id, squeeze_factor): # if none of the tests are available, try again for sputum # requires another appointment - added in ACTUAL_APPT_FOOTPRINT if test_result is None: - if smear_status: test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_sputum_test_smear_positive", hsi_event=self @@ -1861,7 +1860,6 @@ def apply(self, person_id, squeeze_factor): # if still no result available, rely on clinical diagnosis if test_result is None: - test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_clinical", hsi_event=self ) From f56b2d86bdf63ad87ca32b084545d7fa2571e64c Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 25 Oct 2023 15:26:37 +0100 Subject: [PATCH 118/443] initial additions of equipment for delivery care --- src/tlo/methods/labour.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 87fc9384c3..270454f93d 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1866,6 +1866,9 @@ def refer_for_cs(): sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='avd', hsi_event=hsi_event) + # log equipment + hsi_event.EQUIPMENT.update({'Delivery Forceps', 'Vacuum extractor'}) + if avail and sf_check: pregnancy_helper_functions.log_met_need(self, f'avd_{indication}', hsi_event) @@ -2891,6 +2894,11 @@ def apply(self, person_id, squeeze_factor): self.module, self, self.module.item_codes_lab_consumables, core='delivery_core', optional='delivery_optional') + # Log required equipment + self.EQUIPMENT.update({'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 From 8455cff4e60ad4de5b7ba48bc47f42257703afae Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 25 Oct 2023 18:49:13 +0100 Subject: [PATCH 119/443] ac: empty declaration of equipment --- src/tlo/methods/care_of_women_during_pregnancy.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 73a7c1cc31..dec3b72e66 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1442,6 +1442,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AntenatalFirst': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1537,6 +1538,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1632,6 +1634,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1711,6 +1714,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1786,6 +1790,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1859,6 +1864,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1927,6 +1933,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1989,6 +1996,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2045,6 +2053,7 @@ def __init__(self, module, person_id, visit_number): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({('AntenatalFirst' if (self.visit_number == 1) else 'ANCSubsequent'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -2161,6 +2170,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2201,6 +2211,7 @@ def __init__(self, module, person_id): beddays = self.module.calculate_beddays(person_id) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': beddays}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2458,6 +2469,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.ALERT_OTHER_DISEASES = [] + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2507,6 +2519,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.ALERT_OTHER_DISEASES = [] + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2593,6 +2606,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1b' # any hospital? self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 3}) # todo: check with TC + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2683,6 +2697,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 5}) # todo: check with TC + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 389a6e9bba85d30ef3d7075c283b72cc39ace633 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 26 Oct 2023 13:49:30 +0100 Subject: [PATCH 120/443] additions of equipment for PNC --- .../methods/care_of_women_during_pregnancy.py | 7 ++- src/tlo/methods/labour.py | 51 +++++++++++++++---- src/tlo/methods/newborn_outcomes.py | 7 +++ 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index dec3b72e66..095e37b5cc 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -735,6 +735,7 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # Delivery of the intervention is conditioned on a random draw against a probability that the intervention # would be delivered (used to calibrate to SPA data- acts as proxy for clinical quality) if self.rng.random_sample() < params['prob_intervention_delivered_urine_ds']: + hsi_event.EQUIPMENT.update({'Urine dip Stick'}) # check consumables avail = pregnancy_helper_functions.return_cons_avail( @@ -748,16 +749,15 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # We use a temporary variable to store if proteinuria is detected proteinuria_diagnosed = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'dipstick'}) - hsi_event.EQUIPMENT.update({'Urine dip Stick'}) # The process is repeated for blood pressure monitoring if self.rng.random_sample() < params['prob_intervention_delivered_bp']: + hsi_event.EQUIPMENT.update({'Sphygmomanometer'}) if self.sim.modules['HealthSystem'].dx_manager.run_dx_test(dx_tests_to_run='blood_pressure_measurement', hsi_event=hsi_event): hypertension_diagnosed = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'bp_measurement'}) - hsi_event.EQUIPMENT.update({'Sphygmomanometer'}) if not df.at[person_id, 'ac_gest_htn_on_treatment'] and\ (df.at[person_id, 'ps_htn_disorders'] != 'none') and pd.isnull(mni[person_id]['hypertension' @@ -2626,8 +2626,7 @@ def apply(self, person_id, squeeze_factor): self.module, self, self.module.item_codes_preg_consumables, core='post_abortion_care_core', optional='post_abortion_care_optional') - # TODO: equipment set for dilation and cutterage? oxygen? - self.EQUIPMENT.update({'Manual Vacuum aspiration Set', 'Drip stand', 'Infusion pump'}) + self.EQUIPMENT.update({'D&C set', 'Suction Curettage machine', 'Drip stand', 'Infusion pump'}) # Check HCW availability to deliver surgical removal of retained products sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 270454f93d..a6744ca1af 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -2118,17 +2118,29 @@ def surgical_management_of_pph(self, hsi_event): if not mni[person_id]['retained_placenta']: - # We apply a probability that surgical techniques will be effective - treatment_success_pph = params['success_rate_pph_surgery'] > self.rng.random_sample() + if avail and sf_check: + + # Log equipment + # Todo: link to surgical equipment package when that exsists + hsi_event.EQUIPMENT.update( + {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) + + # We apply a probability that surgical techniques will be effective + treatment_success_pph = params['success_rate_pph_surgery'] > self.rng.random_sample() - # And store the treatment which will dramatically reduce risk of death - if treatment_success_pph and avail and sf_check: - self.pph_treatment.set(person_id, 'surgery') + # And store the treatment which will dramatically reduce risk of death + 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 + + # Log equipment + # Todo: link to surgical equipment package when that exsists + hsi_event.EQUIPMENT.update( + {'Hysterectomy set'}) - # If the treatment is unsuccessful then women will require a hysterectomy to stop the bleeding - 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 + self.pph_treatment.set(person_id, 'hysterectomy') + df.at[person_id, 'la_has_had_hysterectomy'] = True # Next we apply the effect of surgical treatment for women with retained placenta elif (mni[person_id]['retained_placenta'] and not self.pph_treatment.has_all(person_id, @@ -2136,6 +2148,11 @@ def surgical_management_of_pph(self, hsi_event): and sf_check and avail): self.pph_treatment.set(person_id, 'surgery') + # Log equipment + # Todo: link to surgical equipment package when that exsists + hsi_event.EQUIPMENT.update( + {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) + # log intervention delivery if self.pph_treatment.has_all(person_id, 'surgery') or df.at[person_id, 'la_has_had_hysterectomy']: pregnancy_helper_functions.log_met_need(self, 'pph_surg', hsi_event) @@ -2162,6 +2179,8 @@ def blood_transfusion(self, hsi_event): hsi_event=hsi_event) if avail and sf_check: + hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) + mni[person_id]['received_blood_transfusion'] = True pregnancy_helper_functions.log_met_need(self, 'blood_tran', hsi_event) @@ -2185,6 +2204,9 @@ def assessment_and_treatment_of_anaemia(self, hsi_event): mother = df.loc[person_id] mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info + # Log equipment + hsi_event.EQUIPMENT.update({'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) @@ -2853,6 +2875,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'NormalDelivery': 1}) self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 2}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -3033,6 +3056,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'PostnatalCare_Maternal' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = self._get_facility_level_for_pnc(person_id) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -3160,6 +3184,7 @@ def __init__(self, module, person_id, timing): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.timing = timing + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -3190,6 +3215,12 @@ def apply(self, person_id, squeeze_factor): hsi_event=self) if avail and sf_check or (mni[person_id]['cs_indication'] == 'other'): + + # If intervention is delivered - log equipment + # Todo: link to surgical equipment package when that exsists + self.EQUIPMENT.update( + {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) + person = df.loc[person_id] logger.info(key='caesarean_delivery', data=person.to_dict()) logger.info(key='cs_indications', data={'id': person_id, @@ -3223,6 +3254,8 @@ 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.EQUIPMENT.update( + {'Hysterectomy set'}) df.at[person_id, 'la_has_had_hysterectomy'] = True # ============================= SURGICAL MANAGEMENT OF POSTPARTUM HAEMORRHAGE================================== diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 1905ac1d9f..604c613c49 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -1008,6 +1008,9 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): df.at[person_id, 'nb_supp_care_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_supportive_care', hsi_event) + # Log equipment + hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) + # The same pattern is then followed for health centre care else: avail = pregnancy_helper_functions.return_cons_avail( @@ -1018,6 +1021,9 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): df.at[person_id, 'nb_inj_abx_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_abx', hsi_event) + # Log equipment + hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump', 'Oxygen cylinder, with regulator'}) + def link_twins(self, child_one, child_two, mother_id): """ This function links twin pairs via sibling IDs and is called by the BirthEvent in the Labour module @@ -1382,6 +1388,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'PostnatalCare_Neonatal' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = self._get_facility_level_for_pnc(person_id) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): nci = self.module.newborn_care_info From f6bd2ad695194190b6c904095d65e049cf1cd69b Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 3 Nov 2023 14:06:45 +0000 Subject: [PATCH 121/443] ALRI equipment added (pulse ox, oxygen) --- src/tlo/methods/alri.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index e4b0b46d17..4915ae8364 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2290,6 +2290,8 @@ def __init__(self, module: Module, person_id: int, facility_level: str = "0", in self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels + self.EQUIPMENT = set() + self.is_followup_following_treatment_failure = is_followup_following_treatment_failure if not inpatient: @@ -2595,6 +2597,8 @@ def _get_disease_classification_for_treatment_decision(self, 'chest_indrawing_pneumonia', (symptoms-based assessment) 'cough_or_cold' (symptoms-based assessment) }.""" + # TODO: Currently this is logged as equipment even if pulse ox consumable isnt available + self.EQUIPMENT.update({'Pulse oximeter'}) child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) @@ -2650,6 +2654,16 @@ def _try_treatment(antibiotic_indicated: Tuple[str], oxygen_indicated: bool) -> oxygen_available = self._get_cons('Oxygen_Therapy') oxygen_provided = (oxygen_available and oxygen_indicated) + # todo: should equipment only be logged if consumables are available? + # If individual requires oxygen, log equipment + if oxygen_indicated: + self.EQUIPMENT.update({'Oxygen cylinder, with regulator', 'Nasal Prongs'}) + + # If individual requires intravenous antibiotics, log equipment + if antibiotic_indicated in ('1st_line_IV_antibiotics', + 'Benzylpenicillin_gentamicin_therapy_for_severe_pneumonia'): + self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + all_things_needed_available = antibiotic_available and ( (oxygen_available and oxygen_indicated) or (not oxygen_indicated) ) @@ -2731,12 +2745,15 @@ def _provide_bronchodilator_if_wheeze(self, facility_level, symptoms): if facility_level == '1a': _ = self._get_cons('Inhaled_Brochodilator') else: + # todo: determine if steroids here are IV (no consumables defined) _ = self._get_cons('Brochodilator_and_Steroids') def do_on_follow_up_following_treatment_failure(self): """Things to do for a patient who is having this HSI following a failure of an earlier treatment. A further drug will be used but this will have no effect on the chance of the person dying.""" + self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + if self._has_staph_aureus(): _ = self._get_cons('2nd_line_Antibiotic_therapy_for_severe_staph_pneumonia') else: From 6e6eecbc8331f157f7a2fd14191dd1727d34de00 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 3 Nov 2023 15:30:34 +0000 Subject: [PATCH 122/443] cardio metabolic disorders equipment added --- src/tlo/methods/cardio_metabolic_disorders.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index 4ef5c41c8e..ed53a942f6 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1391,6 +1391,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "CardioMetabolicDisorders_Prevention_CommunityTestingForHypertension" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1402,6 +1403,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.EQUIPMENT.update({'Blood pressure machine'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='assess_hypertension', hsi_event=self @@ -1442,6 +1444,7 @@ def __init__(self, module, person_id, conditions_to_investigate: List, has_any_c self.ACCEPTED_FACILITY_LEVEL = '1b' self.conditions_to_investigate = conditions_to_investigate self.has_any_cmd_symptom = has_any_cmd_symptom + self.EQUIPMENT = set() def do_for_each_condition(self, _c) -> bool: """What to do for each condition that will be investigated. Returns `bool` signalling whether a follow-up HSI @@ -1454,7 +1457,6 @@ def do_for_each_condition(self, _c) -> bool: if df.at[person_id, f'nc_{_c}_ever_diagnosed']: return - # 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}', hsi_event=self @@ -1486,6 +1488,9 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Do test and trigger treatment (if necessary) for each condition: + if ('diabetes', 'chronic_kidney_disease', 'chronic_ischemic_hd') in self.conditions_to_investigate: + self.EQUIPMENT.update({'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 @@ -1509,6 +1514,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.EQUIPMENT.update({'Blood pressure machine'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='assess_hypertension', hsi_event=self @@ -1545,6 +1551,7 @@ def __init__(self, module, person_id, condition): self.TREATMENT_ID = 'CardioMetabolicDisorders_Prevention_WeightLoss' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() self.condition = condition @@ -1556,6 +1563,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.EQUIPMENT.update({'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), @@ -1692,6 +1701,9 @@ 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.EQUIPMENT.update({'Computed Tomography (CT machine)', 'CT scanner accessories'}) + dx_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run=f'assess_{_ev}', hsi_event=self @@ -1748,6 +1760,11 @@ def apply(self, person_id, squeeze_factor): data=('This is HSI_CardioMetabolicDisorders_SeeksEmergencyCareAndGetsTreatment: ' f'The squeeze-factor is {squeeze_factor}.'), ) + self.EQUIPMENT.update({'Analyser, Combined Chemistry and Electrolytes', + 'Analyser, Haematology', + 'Patient monitor', 'Drip stand', + 'Infusion pump', 'Blood pressure machine', + 'Pulse oximeter', 'Trolley, emergency'}) for _ev in self.events_to_investigate: self.do_for_each_event_to_be_investigated(_ev) From 104c5d5dc0c60b21fbb4bcc2b251b75887e9ed18 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 3 Nov 2023 16:02:25 +0000 Subject: [PATCH 123/443] cardio metabolic disorders equipment added --- src/tlo/methods/cardio_metabolic_disorders.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index ed53a942f6..d3dabdacd9 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1457,6 +1457,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.EQUIPMENT.update({'Stethoscope'}) + dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run=f'assess_{_c}', hsi_event=self @@ -1764,7 +1767,7 @@ def apply(self, person_id, squeeze_factor): 'Analyser, Haematology', 'Patient monitor', 'Drip stand', 'Infusion pump', 'Blood pressure machine', - 'Pulse oximeter', 'Trolley, emergency'}) + 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope'}) for _ev in self.events_to_investigate: self.do_for_each_event_to_be_investigated(_ev) From 9d2e1d4ac9a183663f6068ff7a7627e89a28eb8b Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 3 Nov 2023 16:03:29 +0000 Subject: [PATCH 124/443] cardio metabolic disorders equipment added --- src/tlo/methods/cardio_metabolic_disorders.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index d3dabdacd9..ad4f7071e8 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1767,7 +1767,8 @@ def apply(self, person_id, squeeze_factor): 'Analyser, Haematology', 'Patient monitor', 'Drip stand', 'Infusion pump', 'Blood pressure machine', - 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope'}) + 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope', + 'Oxygen cylinder, with regulator'}) for _ev in self.events_to_investigate: self.do_for_each_event_to_be_investigated(_ev) From 8f1a60cf18006b1fc8cd68c892bb62c0d734e2bc Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 3 Nov 2023 16:21:22 +0000 Subject: [PATCH 125/443] diarrhoea equipment added --- src/tlo/methods/diarrhoea.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tlo/methods/diarrhoea.py b/src/tlo/methods/diarrhoea.py index 242ecb2cc1..6b019a8e4c 100644 --- a/src/tlo/methods/diarrhoea.py +++ b/src/tlo/methods/diarrhoea.py @@ -736,6 +736,7 @@ def do_treatment(self, person_id, hsi_event): # STEP ONE: Aim to alleviate dehydration: prob_remove_dehydration = 0.0 if is_in_patient: + if hsi_event.get_consumables(item_codes=self.consumables_used_in_hsi['Treatment_Severe_Dehydration']): # In-patient receiving IV fluids (WHO Plan C) prob_remove_dehydration = \ @@ -1547,6 +1548,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 2}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """Run `do_treatment` for this person from an in-potient setting.""" @@ -1555,6 +1557,8 @@ def apply(self, person_id, squeeze_factor): if not df.at[person_id, 'is_alive']: return + self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + self.module.do_treatment(person_id=person_id, hsi_event=self) From f7b74b9d324db2db8e1290f1b4e1a4fc98836110 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 10 Nov 2023 08:40:27 +0000 Subject: [PATCH 126/443] initial RTI equipment added. updated confusing naming of consumables as equipment in MNH modules --- .../methods/care_of_women_during_pregnancy.py | 26 +++++++++---------- src/tlo/methods/labour.py | 23 ++++++++-------- src/tlo/methods/newborn_outcomes.py | 12 ++++----- src/tlo/methods/rti.py | 9 ++++++- 4 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 095e37b5cc..8f4b9ac31a 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -215,14 +215,12 @@ def get_and_store_pregnancy_item_codes(self): """ get_list_of_items = pregnancy_helper_functions.get_list_of_items - # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing - self.item_codes_preg_consumables['blood_test_equipment'] = \ + # ---------------------------------- BLOOD TEST CONSUMABLES -------------------------------------------------- + self.item_codes_preg_consumables['blood_test_cons'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) - # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- - self.item_codes_preg_consumables['iv_drug_equipment'] = \ + # ---------------------------------- IV DRUG ADMIN CONSUMABLES ----------------------------------------------- + self.item_codes_preg_consumables['iv_drug_cons'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', 'Disposables gloves, powder free, 100 pieces per box']) @@ -922,7 +920,7 @@ def point_of_care_hb_testing(self, hsi_event): if self.rng.random_sample() < params['prob_intervention_delivered_poct']: logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'hb_screen'}) - hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_equipment']) + hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_cons']) hsi_event.EQUIPMENT.update({'Haemoglobinometer'}) # We run the test through the dx_manager and if a woman has anaemia and its detected she will be admitted @@ -971,7 +969,7 @@ def hep_b_testing(self, hsi_event): # This intervention is a place holder prior to the Hepatitis B module being coded # Define the consumables avail = hsi_event.get_consumables(item_codes=cons['hep_b_test'], - optional_item_codes=cons['blood_test_equipment']) + optional_item_codes=cons['blood_test_cons']) # We log all the consumables required above but we only condition the event test happening on the # availability of the test itself @@ -998,7 +996,7 @@ def syphilis_screening_and_treatment(self, hsi_event): avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_preg_consumables, core='syphilis_test', - optional='blood_test_equipment') + optional='blood_test_cons') test = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run='blood_test_syphilis', hsi_event=hsi_event) @@ -1008,7 +1006,7 @@ def syphilis_screening_and_treatment(self, hsi_event): avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_preg_consumables, core='syphilis_treatment', - optional='blood_test_equipment') + optional='blood_test_cons') if avail: # We assume that treatment is 100% effective at curing infection @@ -1073,7 +1071,7 @@ def gdm_screening(self, hsi_event): if self.rng.random_sample() < params['prob_intervention_delivered_gdm_test']: avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, self.item_codes_preg_consumables, core='gdm_test', optional='iv_drug_equipment') + self, hsi_event, self.item_codes_preg_consumables, core='gdm_test', optional='iv_drug_cons') # If the test accurately detects a woman has gestational diabetes the consumables are recorded and # she is referred for treatment @@ -1249,7 +1247,7 @@ def antenatal_blood_transfusion(self, individual_id, hsi_event): # Check for consumables avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, self.item_codes_preg_consumables, core='blood_transfusion', optional='iv_drug_equipment') + self, hsi_event, self.item_codes_preg_consumables, core='blood_transfusion', optional='iv_drug_cons') sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], sf='blood_tran', @@ -1357,7 +1355,7 @@ def antibiotics_for_prom(self, individual_id, hsi_event): # check consumables and whether HCW are available to deliver the intervention avail = hsi_event.get_consumables(item_codes=cons['abx_for_prom'], - optional_item_codes=cons['iv_drug_equipment']) + optional_item_codes=cons['iv_drug_cons']) sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], sf='iv_abx', @@ -2649,7 +2647,7 @@ def apply(self, person_id, squeeze_factor): cons_for_haemorrhage = pregnancy_helper_functions.return_cons_avail( self.module, self, self.module.item_codes_preg_consumables, core='blood_transfusion', - optional='iv_drug_equipment') + optional='iv_drug_cons') cons_for_shock = pregnancy_helper_functions.return_cons_avail( self.module, self, self.module.item_codes_preg_consumables, core='post_abortion_care_shock', diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index a6744ca1af..ec708a6b84 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -691,16 +691,15 @@ def get_and_store_labour_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items - # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing - self.item_codes_lab_consumables['iv_drug_equipment'] = \ + # ---------------------------------- IV DRUG ADMIN CONSUMABLES ----------------------------------------------- + + self.item_codes_lab_consumables['iv_drug_cons'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', 'Disposables gloves, powder free, 100 pieces per box']) - # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- - self.item_codes_lab_consumables['blood_test_equipment'] = \ + # ---------------------------------- BLOOD TEST CONSUMABLES --------------------------------------------------- + self.item_codes_lab_consumables['blood_test_cons'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) # -------------------------------------------- DELIVERY ------------------------------------------------------ @@ -1660,7 +1659,7 @@ def prophylactic_labour_interventions(self, hsi_event): # If she has not already receive antibiotics, we check for consumables avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, self.item_codes_lab_consumables, core='abx_for_prom', optional='iv_drug_equipment') + self, hsi_event, self.item_codes_lab_consumables, core='abx_for_prom', optional='iv_drug_cons') # Then query if these consumables are available during this HSI And provide if available. # Antibiotics for from reduce risk of newborn sepsis within the first @@ -1675,7 +1674,7 @@ def prophylactic_labour_interventions(self, hsi_event): avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_lab_consumables, core='antenatal_steroids', - optional='iv_drug_equipment') + optional='iv_drug_cons') # If available they are given. Antenatal steroids reduce a preterm newborns chance of developing # respiratory distress syndrome and of death associated with prematurity @@ -1770,7 +1769,7 @@ def assessment_and_treatment_of_hypertension(self, hsi_event, labour_stage): # Then query if these consumables are available during this HSI avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_lab_consumables, core='iv_antihypertensives', - optional='iv_drug_equipment') + optional='iv_drug_cons') # If they are available then the woman is started on treatment. Intravenous antihypertensive reduce a # womans risk of progression from mild to severe gestational hypertension ANd reduce risk of death for @@ -1994,7 +1993,7 @@ def active_management_of_the_third_stage_of_labour(self, hsi_event): # Define and check available consumables avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, self.item_codes_lab_consumables, core='amtsl', optional='iv_drug_equipment') + self, hsi_event, self.item_codes_lab_consumables, core='amtsl', optional='iv_drug_cons') # run HCW check sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='uterotonic', @@ -2172,7 +2171,7 @@ def blood_transfusion(self, hsi_event): # Check consumables avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_lab_consumables, core='blood_transfusion', - optional='iv_drug_equipment') + optional='iv_drug_cons') # check HCW sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='blood_tran', @@ -2211,7 +2210,7 @@ def assessment_and_treatment_of_anaemia(self, hsi_event): test_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run='full_blood_count_hb_pn', hsi_event=hsi_event) - hsi_event.get_consumables(item_codes=self.item_codes_lab_consumables['blood_test_equipment']) + hsi_event.get_consumables(item_codes=self.item_codes_lab_consumables['blood_test_cons']) # Check consumables if test_result: diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 604c613c49..44f5af7c28 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -409,16 +409,16 @@ def get_and_store_newborn_item_codes(self): """ get_list_of_items = pregnancy_helper_functions.get_list_of_items - # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # ---------------------------------- IV DRUG ADMIN CONSUMABLE ------------------------------------------------- # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is # confusing - self.item_codes_nb_consumables['iv_drug_equipment'] = \ + self.item_codes_nb_consumables['iv_drug_cons'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', 'Disposables gloves, powder free, 100 pieces per box']) - # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- - self.item_codes_nb_consumables['blood_test_equipment'] = \ + # ---------------------------------- BLOOD TEST CONSUMABLES --------------------------------------------------- + self.item_codes_nb_consumables['blood_test_cons'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) # -------------------------------------------- VITAMIN K ------------------------------------------ @@ -861,7 +861,7 @@ def essential_newborn_care(self, hsi_event): # We define the consumables avail_eyecare = hsi_event.get_consumables(item_codes=cons['eye_care']) avail_vit_k = hsi_event.get_consumables(item_codes=cons['vitamin_k'], - optional_item_codes=cons['iv_drug_equipment']) + optional_item_codes=cons['iv_drug_cons']) # If they are available the intervention is delivered, there is limited evidence of the effect of these # interventions so currently we are just mapping the consumables @@ -1015,7 +1015,7 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): else: avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_nb_consumables, core='sepsis_abx', - optional='iv_drug_equipment') + optional='iv_drug_cons') if avail and sf_check: df.at[person_id, 'nb_inj_abx_neonatal_sepsis'] = True diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index f92d751e14..9ec5adb9bc 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3428,6 +3428,7 @@ def apply(self, person_id, squeeze_factor): # mortality percentage = 51.2 overall, 50% for TBI admission and 49% for hemorrhage # determine the number of ICU days used to treat patient + # TODO: Add general ICU equipment here? What form of organ support is offered? if df.loc[person_id, 'rt_ISS_score'] > self.hdu_cut_off_iss_score: mean_icu_days = p['mean_icu_days'] sd_icu_days = p['sd_icu_days'] @@ -3722,7 +3723,7 @@ 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 + of shock TODO: is this still the case - should this be scheduled/ignored? """ def __init__(self, module, person_id): @@ -3732,6 +3733,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_ShockTreatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -3743,6 +3745,10 @@ def apply(self, person_id, squeeze_factor): if not df.at[person_id, 'is_alive']: return self.make_appt_footprint({}) get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name + + self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Oxygen cylinder, with regulator', 'Nasal Prongs', + 'Patient monitor', 'Blood pressure machine', 'Pulse oximeter'}) + # TODO: find a more complete list of required consumables for adults if is_child: self.module.item_codes_for_consumables_required['shock_treatment_child'] = { @@ -3824,6 +3830,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_FractureCast' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): # Get the population and health system From 52d09c4069bcea065197e665f7b62a56639ade8b Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 12:11:31 +0100 Subject: [PATCH 127/443] TODO: some modules use equipment when talking about consumables --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 ++ src/tlo/methods/labour.py | 2 ++ src/tlo/methods/newborn_outcomes.py | 2 ++ src/tlo/methods/rti.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 0744bbde79..b710de5ca1 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -216,6 +216,8 @@ def get_and_store_pregnancy_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_preg_consumables['blood_test_equipment'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 92d6a446dd..d9b6118596 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -692,6 +692,8 @@ def get_and_store_labour_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_lab_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 36104cf2ee..1905ac1d9f 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -410,6 +410,8 @@ def get_and_store_newborn_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_nb_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index aac0129cf6..0d788aaf6c 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4665,6 +4665,8 @@ def apply(self, person_id, squeeze_factor): # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, # equipment used by surgeon, gloves and facemask + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From 7f55563b9fb86aa48f32aca331a779d9e8831de3 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:11:12 +0100 Subject: [PATCH 128/443] breast_cancer: dummy used_equipment added where Andrew requested diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 1ce9ad2bf..56c935fba 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -666,6 +666,8 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -759,6 +761,9 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Record used equipment + self.used_equipment = 'Anything used for mastectomy as I guess this is about' + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( --- src/tlo/methods/breast_cancer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1ce9ad2bf6..56c935fba2 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -666,6 +666,8 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -759,6 +761,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Record used equipment + self.used_equipment = 'Anything used for mastectomy as I guess this is about' + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( From 8a0723da185f2852cd83d5b634268f0925640f3e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:15:40 +0100 Subject: [PATCH 129/443] co: dummy used_equipment added for methods where Emi listed some --- src/tlo/methods/contraception.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 4238ae04d2..6ffb0ebc6e 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1255,7 +1255,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 record 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: @@ -1279,6 +1280,13 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Record used equipment when needed + if _new_contraceptive == 'female_sterilization': + self.used_equipment = {'Smt used to sterilize a woman'} + elif _new_contraceptive == 'IUD': + self.used_equipment = {'Equipment used when performing IUD'} + else: _new_contraceptive = "not_using" From c3f88cf09123dffdae542db88c1d0b232adfcde8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:24:02 +0100 Subject: [PATCH 130/443] healthsystem: annual equipment summary log by fac. level --- src/tlo/methods/healthsystem.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index b6a509f1f4..75db01aa27 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,6 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + self.used_equipment = set() @property def bed_days_allocated_to_this_event(self): @@ -1740,6 +1741,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, + equipment=hsi_event.used_equipment, ) def write_to_hsi_log( @@ -1750,6 +1752,7 @@ def write_to_hsi_log( squeeze_factor: float, did_run: bool, priority: int, + equipment: set, ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1779,6 +1782,7 @@ def write_to_hsi_log( squeeze_factor=squeeze_factor, appt_footprint=event_details.appt_footprint, level=event_details.facility_level, + equipment=equipment, ) def call_and_record_never_ran_hsi_event(self, hsi_event, priority=None): @@ -2601,6 +2605,7 @@ def apply(self, population): squeeze_factor=0.0, priority=-1, did_run=True, + equipment=set() # TODO: explore more, should it be non-emtpy in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2655,6 +2660,7 @@ def _reset_internal_stores(self) -> None: self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} # <--Same as `self._appts` but also split by facility_level + self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran @@ -2671,7 +2677,8 @@ def record_hsi_event(self, hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str + level: str, + equipment: set ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2688,6 +2695,9 @@ def record_hsi_event(self, self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number + # Update used equipment by level + self._equip_by_level[level].update(equipment) + def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2750,6 +2760,14 @@ def write_to_log_and_reset_counters(self): }, ) + logger_summary.info( + key="Equipment", + description="Sets of used equipment for each facility level in this calendar year.", + data={ + "Equipment_By_Level": self._equip_by_level, + }, + ) + self._reset_internal_stores() From fc8ab89c8679fa6784ce61d659d7a0bca6389d88 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:35:32 +0100 Subject: [PATCH 131/443] long_run_all_dis: 2K pop, 1 run -- to see results fast --- .../calibration_analyses/scenarios/long_run_all_diseases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py index 8df04fc12e..756af3da53 100644 --- a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py +++ b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py @@ -22,9 +22,9 @@ def __init__(self): self.seed = 0 self.start_date = Date(2010, 1, 1) self.end_date = Date(2030, 1, 1) - self.pop_size = 20_000 + self.pop_size = 2_000 self.number_of_draws = 1 - self.runs_per_draw = 10 + self.runs_per_draw = 1 def log_configuration(self): return { From eab08638e946e151b143c8e5f94a468202748d03 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 16:12:18 +0100 Subject: [PATCH 132/443] Revert "long_run_all_dis: 2K pop, 1 run -- to see results fast" This reverts commit 6d054237a0d44cd0e57c4e46ba21e1332a9c9777. --- .../calibration_analyses/scenarios/long_run_all_diseases.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py index 756af3da53..8df04fc12e 100644 --- a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py +++ b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py @@ -22,9 +22,9 @@ def __init__(self): self.seed = 0 self.start_date = Date(2010, 1, 1) self.end_date = Date(2030, 1, 1) - self.pop_size = 2_000 + self.pop_size = 20_000 self.number_of_draws = 1 - self.runs_per_draw = 1 + self.runs_per_draw = 10 def log_configuration(self): return { From a3842434ed770250dac0984052d8ef837ba484f9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 16:15:18 +0100 Subject: [PATCH 133/443] breast_cancer: mastectomy dummy equipment fixed --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 56c935fba2..5d8fabfcb2 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -762,7 +762,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] # Record used equipment - self.used_equipment = 'Anything used for mastectomy as I guess this is about' + self.used_equipment = {'Anything used for mastectomy as I guess this is about'} # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( From 1d057307d3d6b29f383a7d553fd7e3e0d170ea23 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Sep 2023 21:01:35 +0100 Subject: [PATCH 134/443] equipment_catalogue & utils: new script + a change in utils.py - to create equip. catalogue --- .../equipment/equipment_catalogue.py | 101 ++++++++++++++++++ src/tlo/analysis/utils.py | 8 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/scripts/healthsystem/equipment/equipment_catalogue.py diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py new file mode 100644 index 0000000000..c88a469c2f --- /dev/null +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -0,0 +1,101 @@ +import argparse +import pandas as pd +from pathlib import Path +from tlo.analysis.utils import extract_results +from functools import reduce + + +def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: + """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated + year.""" + + def get_equipment_declaration_by_levels(_df): + """Get the equipment declaration by facility levels for the year.""" + + def unpack_dict_in_series(_raw: pd.Series): + # Create an empty DataFrame to store the data + df = pd.DataFrame() + + # Iterate through the dictionary items + for col_name, mydict in _raw.items(): + for date, inner_dict in mydict.items(): + # Convert the inner_dict to a list of dictionaries with 'date' + data = [{'date': date, 'fac_level': inner_dict_key, 'value': inner_dict_set} for + inner_dict_key, inner_dict_set in inner_dict.items()] + # Create a DataFrame from the list with date & fac_level as indexes + temp_df = pd.DataFrame(data) + temp_df.set_index(['date', 'fac_level'], inplace=True) + temp_df.columns = [None] + + # Concatenate the temporary DataFrame to the result DataFrame + df = pd.concat([df, temp_df]) + + # print(f"\ndf\n {df}") + df.columns = [None] + + return df + + return _df \ + .set_index('date') \ + .pipe(unpack_dict_in_series) \ + .stack() \ + .droplevel(level=2) + + return extract_results( + results_folder, + module='tlo.methods.healthsystem.summary', + key='Equipment', + custom_generate_series=get_equipment_declaration_by_levels + ) + + +def create_equipment_catalogue(results_folder: Path, output_folder: Path): + # Declare path for output file from this script + output_file_name = 'equipment_catalogue_by_level.csv' + output_detailed_file_name = 'equipment_catalogue_by_date_level_sim.csv' + + sim_equipment = get_annual_equipment_declarations_by_levels(results_folder) + sim_equipment_df = pd.DataFrame(sim_equipment) + sim_equipment_df.index.names = ['date', 'fac_level'] + + # Save the detailed CSV + sim_equipment_df.to_csv(output_folder / output_detailed_file_name) + print('equipment_catalogue_by_date_level_sim.csv saved.') + + # Prepare a catalogue only by facility levels + # Define a custom aggregation function to combine sets in columns for each row + def combine_sets(row): + combined_set = set() + for col in row: + combined_set.update(col) + return combined_set + + # Apply the custom aggregation function to each row + sim_equipment_by_level_df = sim_equipment_df.copy() + sim_equipment_by_level_df['equipment'] = sim_equipment_by_level_df.apply(combine_sets, axis=1) + # Group by 'fac_level' and join rows with the same 'fac_level' into one set + sim_equipment_by_level_df.reset_index(inplace=True) + sim_equipment_by_level_df = sim_equipment_by_level_df.groupby('fac_level')['equipment'].apply( + lambda x: list(set.union(*x)) + ).reset_index() + + # Explode the 'equipment' column to separate elements into rows + sim_equipment_by_level_df = sim_equipment_by_level_df.explode('equipment', ignore_index=True).set_index('fac_level') + + # Save the CSV equipment catalogue + sim_equipment_by_level_df.to_csv(output_folder / output_file_name) + print('equipment_catalogue_by_level.csv saved.') + + return 0 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("results_folder", type=Path) + args = parser.parse_args() + + create_equipment_catalogue( + results_folder=args.results_folder, + output_folder=args.results_folder, + ) +# NB. Edit run configuration, the Parameters: "./outputs/sejjej5@ucl.ac.uk/long_run_all_diseases-2023-09-04T233551Z" diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index 52c39e617c..e2d5db608d 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -282,6 +282,9 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series: # get number of draws and numbers of runs info = get_scenario_info(results_folder) + def is_number(element): + return isinstance(element, (int, float)) + # Collect results from each draw/run res = dict() for draw in range(info['number_of_draws']): @@ -293,7 +296,10 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series: df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key] output_from_eval: pd.Series = generate_series(df) assert pd.Series == type(output_from_eval), 'Custom command does not generate a pd.Series' - res[draw_run] = output_from_eval * get_multiplier(draw, run) + if output_from_eval.apply(is_number).all(): + res[draw_run] = output_from_eval * get_multiplier(draw, run) + else: + res[draw_run] = output_from_eval except KeyError: # Some logs could not be found - probably because this run failed. From 58ae081f273437d0ac8c0a0e62b6516c2ef728d6 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 7 Sep 2023 17:12:45 +0100 Subject: [PATCH 135/443] equipment_catalogue: PEP8 --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index c88a469c2f..fdaca54515 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -1,8 +1,9 @@ import argparse -import pandas as pd from pathlib import Path + +import pandas as pd + from tlo.analysis.utils import extract_results -from functools import reduce def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: From 98c07348207005a24d397e8c31277de9f2ed3d25 Mon Sep 17 00:00:00 2001 From: Eva Janouskova <48157464+EvaJanouskova@users.noreply.github.com> Date: Tue, 12 Sep 2023 19:53:10 +0200 Subject: [PATCH 136/443] healthsystem: sort equipment for log Co-authored-by: Tim Hallett <39991060+tbhallett@users.noreply.github.com> --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 75db01aa27..c19b0f4337 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2764,7 +2764,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": self._equip_by_level, + "Equipment_By_Level": sorted(self._equip_by_level), }, ) From 7e588336b87667039013b5d87b95d69d57dd4352 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 18:26:35 +0100 Subject: [PATCH 137/443] equipment_catalogue: comment updated --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index fdaca54515..ecbc206aba 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -8,7 +8,8 @@ def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated - year.""" + year. + NB. healthsystem.summary logger required to have been set at the level INFO or higher.""" def get_equipment_declaration_by_levels(_df): """Get the equipment declaration by facility levels for the year.""" From 0a233ebbd891c4411ecdd8f824f194a2fa3b6ac0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:10:01 +0100 Subject: [PATCH 138/443] rti: unified use of consumables/equipment terms --- src/tlo/methods/rti.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 0d788aaf6c..f92d751e14 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4664,9 +4664,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe @@ -5002,7 +5000,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From 1d6c9149c42587ac2f25ba5b67b83f44c4deea45 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:15:44 +0100 Subject: [PATCH 139/443] hs, brc, co: used_equipment renamed to EQUIPMENT; if equip always same for HSI - set in __init__, otherwise updated in apply diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 5d8fabfcb..26155729a 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -646,6 +646,8 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -666,8 +668,6 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -761,8 +761,9 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Record used equipment - self.used_equipment = {'Anything used for mastectomy as I guess this is about'} + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 6ffb0ebc6..15851e1b7 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1281,11 +1281,12 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) _new_contraceptive = self.new_contraceptive - # Record used equipment when needed + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': - self.used_equipment = {'Smt used to sterilize a woman'} + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': - self.used_equipment = {'Equipment used when performing IUD'} + self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: _new_contraceptive = "not_using" diff --git src/tlo/methods/healthsystem.py src/tlo/methods/healthsystem.py index c19b0f433..3eb6b9940 100644 --- src/tlo/methods/healthsystem.py +++ src/tlo/methods/healthsystem.py @@ -182,7 +182,7 @@ class HSI_Event: self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - self.used_equipment = set() + self.EQUIPMENT = set() @property def bed_days_allocated_to_this_event(self): @@ -1741,7 +1741,7 @@ class HealthSystem(Module): squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.used_equipment, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( --- src/tlo/methods/breast_cancer.py | 9 +++++---- src/tlo/methods/contraception.py | 7 ++++--- src/tlo/methods/healthsystem.py | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 5d8fabfcb2..26155729ab 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,6 +646,8 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -666,8 +668,6 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -761,8 +761,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Record used equipment - self.used_equipment = {'Anything used for mastectomy as I guess this is about'} + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 6ffb0ebc6e..15851e1b77 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1281,11 +1281,12 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Record used equipment when needed + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': - self.used_equipment = {'Smt used to sterilize a woman'} + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': - self.used_equipment = {'Equipment used when performing IUD'} + self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: _new_contraceptive = "not_using" diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index c19b0f4337..3eb6b99400 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,7 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - self.used_equipment = set() + self.EQUIPMENT = set() @property def bed_days_allocated_to_this_event(self): @@ -1741,7 +1741,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.used_equipment, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( From af45487988bdc9680d71005846fafe3f766f7a4c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:04 +0100 Subject: [PATCH 140/443] Revert "utils: typo" This reverts commit e55a396136f0f01155ad2d0c2ccc7676a2aea99b. --- src/tlo/analysis/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index e2d5db608d..c60281b10a 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -263,7 +263,7 @@ def get_multiplier(_draw, _run): )['tlo.methods.population']['scaling_factor']['scaling_factor'].values[0] if custom_generate_series is None: - # If there is no `custom_generate_series` provided, it implies that function required selects the specified + # If there is no `custom_generate_series` provided, it implies that function required selects a the specified # column from the dataframe. assert column is not None, "Must specify which column to extract" else: From b7f2df1683e79ec914c51530d561c4dbfd1b4f38 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:08 +0100 Subject: [PATCH 141/443] Revert "healthsystem: typo" This reverts commit 1bd9a6912e0dd1027782c785ad1e945f09f9b3a1. --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 3eb6b99400..61c012bfb9 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -354,7 +354,7 @@ def initialise(self): # If there are bed-days specified, add (if needed) the in-patient admission and in-patient day Appointment # Types. # (HSI that require a bed for one or more days always need such appointments, but this may have been - # missed in the declaration of the `EXPECTED_APPT_FOOTPRINT` in the HSI.) + # missed in the declaration of the `EXPECTED_APPPT_FOOTPRINT` in the HSI.) # NB. The in-patient day Appointment time is automatically applied on subsequent days. if sum(self.BEDDAYS_FOOTPRINT.values()): self.EXPECTED_APPT_FOOTPRINT = health_system.bed_days.add_first_day_inpatient_appts_to_footprint( From 3bb222c00021047cac894b58c07ced3c2f4de605 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:53:15 +0100 Subject: [PATCH 142/443] Revert "co: comments about consumable items" This reverts commit 950eb7aabb9c1b1dd21958ce2590a66240060bdf. --- src/tlo/methods/contraception.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 15851e1b77..f5b4e4e8aa 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -736,7 +736,6 @@ def get_item_code_for_each_contraceptive(self): get_items_from_pkg = self.sim.modules['HealthSystem'].get_item_codes_from_package_name _cons_codes = dict() - # items for each method that requires an HSI to switch to _cons_codes['pill'] = get_items_from_pkg('Pill') _cons_codes['male_condom'] = get_items_from_pkg('Male condom') _cons_codes['other_modern'] = get_items_from_pkg('Female Condom') @@ -746,7 +745,6 @@ def get_item_code_for_each_contraceptive(self): _cons_codes['implant'] = get_items_from_pkg('Implant') _cons_codes['female_sterilization'] = get_items_from_pkg('Female sterilization') assert set(_cons_codes.keys()) == set(self.states_that_may_require_HSI_to_switch_to) - # items used when initiating a modern reliable method after not using or switching from non-reliable method _cons_codes['co_initiation'] = get_items_from_pkg('Contraception initiation') return _cons_codes From 74228b31a5226b5894ce5d8dd85ed1158f502dad Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:59:27 +0100 Subject: [PATCH 143/443] Revert "healthsyst: comment moved to where it belongs" This reverts commit fbfada1c --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 61c012bfb9..a2af17dadf 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2659,7 +2659,6 @@ def _reset_internal_stores(self) -> None: self._treatment_ids = defaultdict(int) # Running record of the `TREATMENT_ID`s of `HSI_Event`s self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} - # <--Same as `self._appts` but also split by facility_level self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System @@ -2667,6 +2666,7 @@ def _reset_internal_stores(self) -> None: self._never_ran_appts = defaultdict(int) # As above, but for `HSI_Event`s that have never ran self._never_ran_appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} + # <--Same as `self._appts` but also split by facility_level self._frac_time_used_overall = [] # Running record of the usage of the healthcare system self._squeeze_factor_by_hsi_event_name = defaultdict(list) # Running record the squeeze-factor applying to each # treatment_id. Key is of the form: From 625ea738178fa5c84fba2c271d5cd411f64bc4e5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 18 Sep 2023 17:33:16 +0100 Subject: [PATCH 144/443] brc: comment updated diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 26155729a..aef476c87 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -647,7 +647,7 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 26155729ab..aef476c870 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -647,7 +647,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 3557631c0574fb8075dc0fe7d11342d6afde45ba Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:44:03 +0100 Subject: [PATCH 145/443] brc & co: rm the dummy examples of equipment from modules --- src/tlo/methods/breast_cancer.py | 6 ------ src/tlo/methods/contraception.py | 10 +--------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index aef476c870..1ce9ad2bf6 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,8 +646,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -761,10 +759,6 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Update equipment used with treatment - # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher - self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) - # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index f5b4e4e8aa..fdb9c2b848 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1253,8 +1253,7 @@ 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 and record used equipment if any, if not set the contraception to - # "not_using": + # if so do log the availability of all items, 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: @@ -1279,13 +1278,6 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Update equipment when needed - # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher - if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) - elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Equipment used when performing IUD'}) - else: _new_contraceptive = "not_using" From ad7bd04c8db8d40766aa5f8381836c5cd5edb8ea Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:54:51 +0100 Subject: [PATCH 146/443] brc: changes in comments moved to #1105 --- src/tlo/methods/breast_cancer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1ce9ad2bf6..7760340088 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -574,7 +574,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) - # scheduled to run every month: do not change as this is hard-wired into the values of all the parameters. + # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. def apply(self, population): df = population.props # shortcut to dataframe @@ -645,7 +645,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. def apply(self, person_id, squeeze_factor): df = self.sim.population.props From e078b3e5b99da497185f57c4529c58b94facd7d5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 18:06:37 +0100 Subject: [PATCH 147/443] [no ci] co: rm empty line (unintentionally added) --- src/tlo/methods/contraception.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index fdb9c2b848..5b545ad158 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1277,7 +1277,6 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive - else: _new_contraceptive = "not_using" From d1fd95f652354b26475b9cd8a43d2d270189b1fe Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 24 Sep 2023 16:15:42 +0100 Subject: [PATCH 148/443] RF_Equipment: equipment catalogue - first draft (from Sakshi) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv new file mode 100644 index 0000000000..b119a1d503 --- /dev/null +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17a3a35a9c7908c9a291b8906ae1f8f7be6ff89459a183900de142ee29368c53 +size 36731 From 6f11df797b629c1c19f08d6ee6f41d221054911b Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 24 Sep 2023 23:54:02 +0100 Subject: [PATCH 149/443] RF_Equipment: equipment catalogue - merge duplicates (round 1) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index b119a1d503..4e936f4cb9 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17a3a35a9c7908c9a291b8906ae1f8f7be6ff89459a183900de142ee29368c53 -size 36731 +oid sha256:faf0337415b4ef87fc47dace921c9f9fb4450d88ff1718def0a96e80fa073cb6 +size 34520 From 7e40b7bf920c66b5e0618ffebecfab80f3d5efb8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 09:56:16 +0100 Subject: [PATCH 150/443] RF_Equipment: equipment catalogue - merge duplicates (round 2) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 4e936f4cb9..22f62d5a0f 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:faf0337415b4ef87fc47dace921c9f9fb4450d88ff1718def0a96e80fa073cb6 -size 34520 +oid sha256:b4f78c47032a3e658fc1d07fd631c2e8aea85b145a7978fcd5d965bf9e0c5fce +size 32543 From 595e5a838503388601f34c84db6c459c6e548c74 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 21:50:20 +0100 Subject: [PATCH 151/443] hs: debugging Equipment log - rm sorted --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index a2af17dadf..aab59465ba 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2764,7 +2764,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": sorted(self._equip_by_level), + "Equipment_By_Level": self._equip_by_level, }, ) From 7818d8f91d51ae8dc89873ea60fb05437ff69d1d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 28 Sep 2023 00:49:34 +0100 Subject: [PATCH 152/443] hs: fix sorting of _equip_by_level --- src/tlo/methods/healthsystem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index aab59465ba..0e620bc436 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2760,6 +2760,9 @@ def write_to_log_and_reset_counters(self): }, ) + # Sort equipment within levels, and log them + for key in self._equip_by_level: + self._equip_by_level[key] = sorted(self._equip_by_level[key]) logger_summary.info( key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", From b9d97a3768d50726c7336082063ba8c12733025d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 19:08:35 +0100 Subject: [PATCH 153/443] RF_Equipment: equipment catalogue - merge duplicates (round 3) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 22f62d5a0f..6178c242ba 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4f78c47032a3e658fc1d07fd631c2e8aea85b145a7978fcd5d965bf9e0c5fce -size 32543 +oid sha256:9f02f434986e8070dacc50405e2b88826be18eb6de61b4e6475e7a4cad948334 +size 32455 From 5b527ca82e49446b6a70f155757d1f06cd09f569 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 28 Sep 2023 00:32:23 +0100 Subject: [PATCH 154/443] RF_Equipment: equipment catalogue - merge duplicates (round 4) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 6178c242ba..aca3040b03 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f02f434986e8070dacc50405e2b88826be18eb6de61b4e6475e7a4cad948334 -size 32455 +oid sha256:29782fdacd7b13efef9b0b9de23fb7413b329bb9880b87c3b0522e6732130bc1 +size 31872 From 37bceba09792ecfcff8f07f25c7806fc99d9fc6d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 29 Sep 2023 10:45:25 +0100 Subject: [PATCH 155/443] RF_Equipment: equipment catalogue - merge duplicates (round 5) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index aca3040b03..0055cd8d8e 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29782fdacd7b13efef9b0b9de23fb7413b329bb9880b87c3b0522e6732130bc1 -size 31872 +oid sha256:f3854096278164e1e3c63c2aa2e7afecbae1c6ee14dabcff829af78c271cd82d +size 31712 From 68f9a5266bc6c28180fa2212c9374d19bcc602e1 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:09:49 +0000 Subject: [PATCH 156/443] RF_Equipment: equipment catalogue - merge duplicates (round 6) + col names renamed --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 0055cd8d8e..c7f2e7e240 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3854096278164e1e3c63c2aa2e7afecbae1c6ee14dabcff829af78c271cd82d -size 31712 +oid sha256:c1f647adbbd18cbc1460135450d2f0b402fac00f80f050fcb3cd8c138b822f71 +size 31663 From 5f610688258c7984a8fd62b8e8c1976d37e5acda Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:23:41 +0000 Subject: [PATCH 157/443] RF_Equip: equip item codes added --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index c7f2e7e240..f0a2b53c9f 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1f647adbbd18cbc1460135450d2f0b402fac00f80f050fcb3cd8c138b822f71 -size 31663 +oid sha256:cd2c3558a4d30dfb6d913054513e1d6df1b91f4955b60371e762eff05ac99afd +size 32759 From 6d9f6c017b8d0896aa9a0003f8970f82e9a4ba89 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:21:55 +0000 Subject: [PATCH 158/443] codes_to_items_list: new script created --- src/tlo/analysis/codes_to_items_list.py | 70 +++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/tlo/analysis/codes_to_items_list.py diff --git a/src/tlo/analysis/codes_to_items_list.py b/src/tlo/analysis/codes_to_items_list.py new file mode 100644 index 0000000000..365f2e64b8 --- /dev/null +++ b/src/tlo/analysis/codes_to_items_list.py @@ -0,0 +1,70 @@ +""" +(1) Can be used for a list of items without item codes yet saved in a csv file named 'csv_file_to_update_name'. + +This script will assign unique code to each unique item name which has no code assigned yet. The codes are +assigned in order from the sequence 0, 1, 2, .... + +Duplicated items are allowed, the same code will be assigned to the same items. + +(2) Can be used when new items are added later without item codes but some items with codes are already in the list. + +This script will keep the existing codes for items with already assigned code and for items without existing +code will assign new code (continue in sequence, i.e. if the highest code is 5, it assigns new codes from the continuing +sequence 6, 7, 8, ...). + +------ +NB. Make sure the 'csv_file_to_update_name' is the file you want to update. The output will be named +'csv_file_to_update_name' + '_new.csv' to avoid unintentionally losing the previous version. +------ +""" + +import pandas as pd +from pathlib import Path + + +# ## CHANGE THIS IF YOU WANT TO USE DIFFERENT FILE AS INPUT +csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' + +# Get the path of the current script file +script_path = Path(__file__) +print(script_path) + +# Specify the file path to RF csv file +file_path = script_path.parent.parent.parent.parent / 'resources/healthsystem/infrastructure_and_equipment' + +# Load the CSV RF into a DataFrame +df = pd.read_csv(Path(file_path) / str(csv_file_to_update_name + '.csv')) + +# Find unique values in Equipment that have no code and are not None or empty +unique_values =\ + df.loc[df['Equip_Code'].isna() & df['Equip_Item'].notna() & (df['Equip_Item'] != ''), 'Equip_Item'].unique() + +# Create a mapping of unique values to codes +value_to_code = {} +# Initialize the starting code value +if not df['Equip_Code'].isna().all(): + next_code = int(df['Equip_Code'].max()) + 1 +else: + next_code = 0 + +# Iterate through unique values +for value in unique_values: + # Check if there is at least one existing code for this value + matching_rows = df.loc[df['Equip_Item'] == value, 'Equip_Code'].dropna() + if not matching_rows.empty: + # Use the existing code for this value + existing_code = int(matching_rows.iloc[0]) + # TODO: verify all the codes are the same + else: + # If no existing codes, start with the next available code + existing_code = next_code + next_code += 1 + value_to_code[value] = existing_code + # Update the 'Equip_Code' column for matching rows + df.loc[df['Equip_Item'] == value, 'Equip_Code'] = existing_code + +# Convert 'Equip_Code' column to integers +df['Equip_Code'] = df['Equip_Code'].astype('Int64') # Convert to nullable integer type + +# Save CSV with equipment codes +df.to_csv(Path(file_path) / str(csv_file_to_update_name + '_new.csv'), index=False) From 5b5c41d6ab2ff8d073f8bdea6083d90c4de765a6 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:45:42 +0000 Subject: [PATCH 159/443] codes_to_items_list: script generalised + PEP 8 --- src/tlo/analysis/codes_to_items_list.py | 31 ++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/tlo/analysis/codes_to_items_list.py b/src/tlo/analysis/codes_to_items_list.py index 365f2e64b8..da169a06e5 100644 --- a/src/tlo/analysis/codes_to_items_list.py +++ b/src/tlo/analysis/codes_to_items_list.py @@ -18,39 +18,44 @@ ------ """ -import pandas as pd from pathlib import Path - -# ## CHANGE THIS IF YOU WANT TO USE DIFFERENT FILE AS INPUT -csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' +import pandas as pd # Get the path of the current script file script_path = Path(__file__) print(script_path) -# Specify the file path to RF csv file +# ############################# +# ## CHANGE THIS FOR YOUR FILE +# Specify name of the csv file +csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' +# Specify the file path to csv file file_path = script_path.parent.parent.parent.parent / 'resources/healthsystem/infrastructure_and_equipment' +# Specify the names of columns containing the item names and item codes +item_col_name = 'Equip_Item' +code_col_name = 'Equip_Code' +# ############################# # Load the CSV RF into a DataFrame df = pd.read_csv(Path(file_path) / str(csv_file_to_update_name + '.csv')) # Find unique values in Equipment that have no code and are not None or empty unique_values =\ - df.loc[df['Equip_Code'].isna() & df['Equip_Item'].notna() & (df['Equip_Item'] != ''), 'Equip_Item'].unique() + df.loc[df[code_col_name].isna() & df[item_col_name].notna() & (df[item_col_name] != ''), item_col_name].unique() # Create a mapping of unique values to codes value_to_code = {} # Initialize the starting code value -if not df['Equip_Code'].isna().all(): - next_code = int(df['Equip_Code'].max()) + 1 +if not df[code_col_name].isna().all(): + next_code = int(df[code_col_name].max()) + 1 else: next_code = 0 # Iterate through unique values for value in unique_values: # Check if there is at least one existing code for this value - matching_rows = df.loc[df['Equip_Item'] == value, 'Equip_Code'].dropna() + matching_rows = df.loc[df[item_col_name] == value, code_col_name].dropna() if not matching_rows.empty: # Use the existing code for this value existing_code = int(matching_rows.iloc[0]) @@ -60,11 +65,11 @@ existing_code = next_code next_code += 1 value_to_code[value] = existing_code - # Update the 'Equip_Code' column for matching rows - df.loc[df['Equip_Item'] == value, 'Equip_Code'] = existing_code + # Update the code_col_name column for matching rows + df.loc[df[item_col_name] == value, code_col_name] = existing_code -# Convert 'Equip_Code' column to integers -df['Equip_Code'] = df['Equip_Code'].astype('Int64') # Convert to nullable integer type +# Convert code_col_name column to integers +df[code_col_name] = df[code_col_name].astype('Int64') # Convert to nullable integer type # Save CSV with equipment codes df.to_csv(Path(file_path) / str(csv_file_to_update_name + '_new.csv'), index=False) From adefc49322e697e29dbb33d8808eb7d0a1e71497 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 16 Nov 2023 17:55:35 +0000 Subject: [PATCH 160/443] hs: equipment added to HSIEventDetails --- src/tlo/methods/healthsystem.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 0e620bc436..bcbe6c465a 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -126,6 +126,7 @@ class HSIEventDetails(NamedTuple): facility_level: Optional[str] appt_footprint: Tuple[Tuple[str, int]] beddays_footprint: Tuple[Tuple[str, int]] + equipment: set class HSIEventQueueItem(NamedTuple): @@ -399,7 +400,8 @@ def as_namedtuple( appt_footprint=tuple(sorted(appt_footprint.items())), beddays_footprint=tuple( sorted((k, v) for k, v in self.BEDDAYS_FOOTPRINT.items() if v > 0) - ) + ), + equipment=(tuple(self.EQUIPMENT)) ) @@ -1767,10 +1769,13 @@ def write_to_hsi_log( 'did_run': did_run, 'Facility_Level': event_details.facility_level if event_details.facility_level is not None else -99, 'Facility_ID': facility_id if facility_id is not None else -99, + 'equipment': equipment, }, description="record of each HSI event" ) if did_run: + print("\nevent_details") + print(event_details) if self._hsi_event_count_log_period is not None: event_details_key = self._hsi_event_details.setdefault( event_details, len(self._hsi_event_details) @@ -1815,7 +1820,7 @@ def write_to_never_ran_hsi_log( event_details: HSIEventDetails, person_id: int, facility_id: Optional[int], - priority: int, + priority: int ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1840,7 +1845,7 @@ def write_to_never_ran_hsi_log( treatment_id=event_details.treatment_id, hsi_event_name=event_details.event_name, appt_footprint=event_details.appt_footprint, - level=event_details.facility_level, + level=event_details.facility_level ) def log_current_capabilities_and_usage(self): @@ -2100,7 +2105,7 @@ def run_individual_level_events_in_mode_0_or_1(self, actual_appt_footprint=actual_appt_footprint, squeeze_factor=squeeze_factor, did_run=True, - priority=_priority + priority=_priority, ) # if not ok_to_run @@ -2598,7 +2603,8 @@ def apply(self, population): treatment_id='Inpatient_Care', facility_level=self.module._facility_by_facility_id[_fac_id].level, appt_footprint=tuple(sorted(_inpatient_appts.items())), - beddays_footprint=() + beddays_footprint=(), + equipment=() # TODO: what should be in here? ), person_id=-1, facility_id=_fac_id, From abb622874ad859d3b72c33049d4f2f0fd038d7d1 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 17 Nov 2023 18:51:25 +0000 Subject: [PATCH 161/443] equip_catalogue: make catalogues from new logging (equip included in hsi_event_details, counts logged in hsi_event_counts) --- .../equipment/equipment_catalogue.py | 117 +++++++++++------- 1 file changed, 69 insertions(+), 48 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index ecbc206aba..9672c92474 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -3,36 +3,35 @@ import pandas as pd -from tlo.analysis.utils import extract_results +from tlo.analysis.utils import extract_results, load_pickled_dataframes -def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: - """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated - year. - NB. healthsystem.summary logger required to have been set at the level INFO or higher.""" +def get_annual_hsi_event_counts(results_folder: Path) -> pd.DataFrame: + """Return pd.DataFrame gives the simulated annual counts of all the hsi event details logged (details as keys) + for each simulated year. + NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" - def get_equipment_declaration_by_levels(_df): - """Get the equipment declaration by facility levels for the year.""" + def get_hsi_event_counts(_df): + """Get the counts of all the hsi event details logged.""" def unpack_dict_in_series(_raw: pd.Series): # Create an empty DataFrame to store the data df = pd.DataFrame() # Iterate through the dictionary items - for col_name, mydict in _raw.items(): + for _, mydict in _raw.items(): for date, inner_dict in mydict.items(): # Convert the inner_dict to a list of dictionaries with 'date' - data = [{'date': date, 'fac_level': inner_dict_key, 'value': inner_dict_set} for + data = [{'date': date, 'event_details_key': inner_dict_key, 'count': inner_dict_set} for inner_dict_key, inner_dict_set in inner_dict.items()] # Create a DataFrame from the list with date & fac_level as indexes temp_df = pd.DataFrame(data) - temp_df.set_index(['date', 'fac_level'], inplace=True) + temp_df.set_index(['date', 'event_details_key'], inplace=True) temp_df.columns = [None] # Concatenate the temporary DataFrame to the result DataFrame df = pd.concat([df, temp_df]) - # print(f"\ndf\n {df}") df.columns = [None] return df @@ -46,47 +45,69 @@ def unpack_dict_in_series(_raw: pd.Series): return extract_results( results_folder, module='tlo.methods.healthsystem.summary', - key='Equipment', - custom_generate_series=get_equipment_declaration_by_levels + key='hsi_event_counts', + custom_generate_series=get_hsi_event_counts ) -def create_equipment_catalogue(results_folder: Path, output_folder: Path): - # Declare path for output file from this script - output_file_name = 'equipment_catalogue_by_level.csv' - output_detailed_file_name = 'equipment_catalogue_by_date_level_sim.csv' +def get_hsi_event_keys(results_folder: Path): + return load_pickled_dataframes(results_folder, 0, 0, "tlo.methods.healthsystem.summary")[ + "tlo.methods.healthsystem.summary" + ]["hsi_event_details"]["hsi_event_key_to_event_details"][0] - sim_equipment = get_annual_equipment_declarations_by_levels(results_folder) + +def create_equipment_catalogues(results_folder: Path, output_folder: Path): + + # Declare output file names + output_detailed_file_name = 'equipment_annual_counts__all_event_details.csv' + output_file_name = 'equipment_annual_counts__by_Date_EventName_FacLevel.csv' + + # %% Catalogue equipment by all HSI event details + sim_equipment = get_annual_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) - sim_equipment_df.index.names = ['date', 'fac_level'] - - # Save the detailed CSV - sim_equipment_df.to_csv(output_folder / output_detailed_file_name) - print('equipment_catalogue_by_date_level_sim.csv saved.') - - # Prepare a catalogue only by facility levels - # Define a custom aggregation function to combine sets in columns for each row - def combine_sets(row): - combined_set = set() - for col in row: - combined_set.update(col) - return combined_set - - # Apply the custom aggregation function to each row - sim_equipment_by_level_df = sim_equipment_df.copy() - sim_equipment_by_level_df['equipment'] = sim_equipment_by_level_df.apply(combine_sets, axis=1) - # Group by 'fac_level' and join rows with the same 'fac_level' into one set - sim_equipment_by_level_df.reset_index(inplace=True) - sim_equipment_by_level_df = sim_equipment_by_level_df.groupby('fac_level')['equipment'].apply( - lambda x: list(set.union(*x)) - ).reset_index() - - # Explode the 'equipment' column to separate elements into rows - sim_equipment_by_level_df = sim_equipment_by_level_df.explode('equipment', ignore_index=True).set_index('fac_level') - - # Save the CSV equipment catalogue - sim_equipment_by_level_df.to_csv(output_folder / output_file_name) - print('equipment_catalogue_by_level.csv saved.') + sim_equipment_df.fillna(0, inplace=True) + + hsi_event_keys = get_hsi_event_keys(results_folder) + + decoded_keys = sim_equipment_df.index.get_level_values(1).astype(str).map(hsi_event_keys) + sim_equipment_df = pd.concat([sim_equipment_df, pd.DataFrame(decoded_keys.tolist(), index=sim_equipment_df.index)], axis=1) + # Make values in 'appt_footprint', and 'beddays_footprint' columns to be string + sim_equipment_df['appt_footprint'] = sim_equipment_df['appt_footprint'].apply(lambda x: ', '.join(map(str, x))) + sim_equipment_df['beddays_footprint'] = sim_equipment_df['beddays_footprint'].apply(lambda x: ', '.join(map(str, x))) + # Explode the 'equipment' column + exploded_df = sim_equipment_df.explode('equipment') + # Remove the 'event_details_key' and replace the index with hsi event details as indexes + exploded_df = exploded_df.droplevel(level=1) + exploded_df = exploded_df.set_index( + ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', + 'equipment'], append=True + ) + # Sum values with the same multi-index + exploded_df = exploded_df.groupby(exploded_df.index).sum() + # Convert the index back to a MultiIndex + exploded_df.index = pd.MultiIndex.from_tuples(exploded_df.index) + exploded_df.index.names = \ + ['date', 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', + 'equipment'] + + # Save the detailed equipment catalogue by levels + exploded_df.to_csv(output_folder / output_detailed_file_name) + print(f'{output_detailed_file_name} saved.') + # --- + + # %% Catalogue equipment by Facility Levels and HSI Event Names + + equipment_counts_by_date_hsi_name_level_df = exploded_df.copy() + + # Sum counts for each equipment with the same date, hsi event name, and level (remaining indexes removed) + # equipment_counts_by_date_hsi_name_level_df + equipment_counts_by_date_hsi_name_level_df = \ + equipment_counts_by_date_hsi_name_level_df.groupby(['date', 'event_name', 'facility_level', 'equipment']).sum() + + # Save the CSV equipment counts + equipment_counts_by_date_hsi_name_level_df.to_csv(output_folder / output_file_name) + print(f'{output_file_name} saved.') + # --- return 0 @@ -96,7 +117,7 @@ def combine_sets(row): parser.add_argument("results_folder", type=Path) args = parser.parse_args() - create_equipment_catalogue( + create_equipment_catalogues( results_folder=args.results_folder, output_folder=args.results_folder, ) From cfa671d03650677744893fb14cdfebca954bb340 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 19 Nov 2023 17:59:41 +0000 Subject: [PATCH 162/443] hs: old logging rm; accidentally rm/added commas/comments returned --- src/tlo/methods/healthsystem.py | 31 ++++++------------------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index bcbe6c465a..f7c5e68f5a 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1774,8 +1774,6 @@ def write_to_hsi_log( description="record of each HSI event" ) if did_run: - print("\nevent_details") - print(event_details) if self._hsi_event_count_log_period is not None: event_details_key = self._hsi_event_details.setdefault( event_details, len(self._hsi_event_details) @@ -1787,7 +1785,6 @@ def write_to_hsi_log( squeeze_factor=squeeze_factor, appt_footprint=event_details.appt_footprint, level=event_details.facility_level, - equipment=equipment, ) def call_and_record_never_ran_hsi_event(self, hsi_event, priority=None): @@ -1820,7 +1817,7 @@ def write_to_never_ran_hsi_log( event_details: HSIEventDetails, person_id: int, facility_id: Optional[int], - priority: int + priority: int, ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1845,7 +1842,7 @@ def write_to_never_ran_hsi_log( treatment_id=event_details.treatment_id, hsi_event_name=event_details.event_name, appt_footprint=event_details.appt_footprint, - level=event_details.facility_level + level=event_details.facility_level, ) def log_current_capabilities_and_usage(self): @@ -2105,7 +2102,7 @@ def run_individual_level_events_in_mode_0_or_1(self, actual_appt_footprint=actual_appt_footprint, squeeze_factor=squeeze_factor, did_run=True, - priority=_priority, + priority=_priority ) # if not ok_to_run @@ -2611,7 +2608,7 @@ def apply(self, population): squeeze_factor=0.0, priority=-1, did_run=True, - equipment=set() # TODO: explore more, should it be non-emtpy in some cases? + equipment=set(), # TODO: explore more, should it be non-emtpy in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2665,14 +2662,13 @@ def _reset_internal_stores(self) -> None: self._treatment_ids = defaultdict(int) # Running record of the `TREATMENT_ID`s of `HSI_Event`s self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} - self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} + # <--Same as `self._appts` but also split by facility_level # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran self._never_ran_appts = defaultdict(int) # As above, but for `HSI_Event`s that have never ran self._never_ran_appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} - # <--Same as `self._appts` but also split by facility_level self._frac_time_used_overall = [] # Running record of the usage of the healthcare system self._squeeze_factor_by_hsi_event_name = defaultdict(list) # Running record the squeeze-factor applying to each # treatment_id. Key is of the form: @@ -2683,8 +2679,7 @@ def record_hsi_event(self, hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str, - equipment: set + level: str ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2701,9 +2696,6 @@ def record_hsi_event(self, self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number - # Update used equipment by level - self._equip_by_level[level].update(equipment) - def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2766,17 +2758,6 @@ def write_to_log_and_reset_counters(self): }, ) - # Sort equipment within levels, and log them - for key in self._equip_by_level: - self._equip_by_level[key] = sorted(self._equip_by_level[key]) - logger_summary.info( - key="Equipment", - description="Sets of used equipment for each facility level in this calendar year.", - data={ - "Equipment_By_Level": self._equip_by_level, - }, - ) - self._reset_internal_stores() From 2152f3ac94cda1688f08e7e6db6086f39b8ee217 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 19 Nov 2023 16:44:27 +0000 Subject: [PATCH 163/443] hs: sort equipment for logging --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index f7c5e68f5a..cec712ef18 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -401,7 +401,7 @@ def as_namedtuple( beddays_footprint=tuple( sorted((k, v) for k, v in self.BEDDAYS_FOOTPRINT.items() if v > 0) ), - equipment=(tuple(self.EQUIPMENT)) + equipment=(tuple(sorted(self.EQUIPMENT))) ) From 8c28f4768e54097bdf0b7aa5cc35c5f660ecea0f Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 19 Nov 2023 18:20:29 +0000 Subject: [PATCH 164/443] utils, brc, co & hs: fix mistakes from rebase; TODO added --- src/tlo/analysis/utils.py | 2 +- src/tlo/methods/breast_cancer.py | 5 +++-- src/tlo/methods/contraception.py | 6 ++++-- src/tlo/methods/healthsystem.py | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index c60281b10a..e2d5db608d 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -263,7 +263,7 @@ def get_multiplier(_draw, _run): )['tlo.methods.population']['scaling_factor']['scaling_factor'].values[0] if custom_generate_series is None: - # If there is no `custom_generate_series` provided, it implies that function required selects a the specified + # If there is no `custom_generate_series` provided, it implies that function required selects the specified # column from the dataframe. assert column is not None, "Must specify which column to extract" else: diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 7760340088..2529911d84 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -574,7 +574,7 @@ class BreastCancerMainPollingEvent(RegularEvent, PopulationScopeEventMixin): def __init__(self, module): super().__init__(module, frequency=DateOffset(months=1)) - # scheduled to run every 3 months: do not change as this is hard-wired into the values of all the parameters. + # scheduled to run every month: do not change as this is hard-wired into the values of all the parameters. def apply(self, population): df = population.props # shortcut to dataframe @@ -645,7 +645,8 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) - self.ACCEPTED_FACILITY_LEVEL = '3' # Mammography only available at level 3 and above. + self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + # TODO: but the appt footprints suggests mammography to be provided def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 5b545ad158..88736c98cb 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -353,7 +353,7 @@ def avoid_sterilization_below30(probs): # Increase prob of 'female_sterilization' in older women accordingly probs_30plus = probs.copy() probs_30plus['female_sterilization'] = ( - probs.loc['female_sterilization'] / + probs.loc['female_sterilization'] / self.ratio_n_females_30_49_to_15_49_in_2010 ) # Scale so that the probability of all outcomes sum to 1.0 @@ -633,7 +633,7 @@ def avoid_sterilization_below30(probs): # Increase prob of 'female_sterilization' in older women accordingly probs_30plus = probs.copy() probs_30plus['female_sterilization'] = ( - probs.loc['female_sterilization'] / + probs.loc['female_sterilization'] / self.ratio_n_females_30_49_to_15_49_in_2010 ) # Scale so that the probability of all outcomes sum to 1.0 @@ -736,6 +736,7 @@ def get_item_code_for_each_contraceptive(self): get_items_from_pkg = self.sim.modules['HealthSystem'].get_item_codes_from_package_name _cons_codes = dict() + # items for each method that requires an HSI to switch to _cons_codes['pill'] = get_items_from_pkg('Pill') _cons_codes['male_condom'] = get_items_from_pkg('Male condom') _cons_codes['other_modern'] = get_items_from_pkg('Female Condom') @@ -745,6 +746,7 @@ def get_item_code_for_each_contraceptive(self): _cons_codes['implant'] = get_items_from_pkg('Implant') _cons_codes['female_sterilization'] = get_items_from_pkg('Female sterilization') assert set(_cons_codes.keys()) == set(self.states_that_may_require_HSI_to_switch_to) + # items used when initiating a modern reliable method after not using or switching from non-reliable method _cons_codes['co_initiation'] = get_items_from_pkg('Contraception initiation') return _cons_codes diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index cec712ef18..92520004d3 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -355,7 +355,7 @@ def initialise(self): # If there are bed-days specified, add (if needed) the in-patient admission and in-patient day Appointment # Types. # (HSI that require a bed for one or more days always need such appointments, but this may have been - # missed in the declaration of the `EXPECTED_APPPT_FOOTPRINT` in the HSI.) + # missed in the declaration of the `EXPECTED_APPT_FOOTPRINT` in the HSI.) # NB. The in-patient day Appointment time is automatically applied on subsequent days. if sum(self.BEDDAYS_FOOTPRINT.values()): self.EXPECTED_APPT_FOOTPRINT = health_system.bed_days.add_first_day_inpatient_appts_to_footprint( From 11aa3f98872bf5b9d226bf67d785e86b8e375893 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 19 Nov 2023 23:58:02 +0000 Subject: [PATCH 165/443] test_hs: assert equipment logging within detailed_hsi_event --- tests/test_healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_healthsystem.py b/tests/test_healthsystem.py index 62a39aea3c..f5b95c74c4 100644 --- a/tests/test_healthsystem.py +++ b/tests/test_healthsystem.py @@ -861,7 +861,7 @@ def apply(self, person_id, squeeze_factor): detailed_consumables = log["tlo.methods.healthsystem"]['Consumables'] assert {'date', 'TREATMENT_ID', 'did_run', 'Squeeze_Factor', 'priority', 'Number_By_Appt_Type_Code', 'Person_ID', - 'Facility_Level', 'Facility_ID', 'Event_Name', + 'Facility_Level', 'Facility_ID', 'Event_Name', 'equipment' } == set(detailed_hsi_event.columns) assert {'date', 'Frac_Time_Used_Overall', 'Frac_Time_Used_By_Facility_ID', 'Frac_Time_Used_By_OfficerType', } == set(detailed_capacity.columns) From 6f6d21fcfe5cb4b46891f911e44992b9dc8e0bb0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 23 Nov 2023 12:00:46 +0000 Subject: [PATCH 166/443] [no ci] hs: typo --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 92520004d3..6a34137f71 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2608,7 +2608,7 @@ def apply(self, population): squeeze_factor=0.0, priority=-1, did_run=True, - equipment=set(), # TODO: explore more, should it be non-emtpy in some cases? + equipment=set(), # TODO: explore more, should it be non-empty in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. From d6ab1682491435aee421358464449bad4c273f6c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Dec 2023 23:16:05 +0000 Subject: [PATCH 167/443] equip_catalogue: fix the keys mapping to be done for each run --- .../equipment/equipment_catalogue.py | 157 ++++++++++++------ 1 file changed, 104 insertions(+), 53 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 9672c92474..1ba0f83cb4 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -1,14 +1,23 @@ import argparse +import warnings from pathlib import Path import pandas as pd -from tlo.analysis.utils import extract_results, load_pickled_dataframes +from tlo.analysis.utils import extract_results +# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# Declare whether to scale the counts to Malawi population size +do_scaling = True +# Declare output file names +output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' +output_file_name = 'equipment_annual_counts__by_Year_TreatmentID_FacLevel.csv' +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -def get_annual_hsi_event_counts(results_folder: Path) -> pd.DataFrame: - """Return pd.DataFrame gives the simulated annual counts of all the hsi event details logged (details as keys) - for each simulated year. + +def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: + """Returned pd.DataFrame gives the monthly counts of all the hsi event details logged (details as keys) + for each simulated month. NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" def get_hsi_event_counts(_df): @@ -46,68 +55,110 @@ def unpack_dict_in_series(_raw: pd.Series): results_folder, module='tlo.methods.healthsystem.summary', key='hsi_event_counts', - custom_generate_series=get_hsi_event_counts + custom_generate_series=get_hsi_event_counts, + do_scaling=do_scaling ) -def get_hsi_event_keys(results_folder: Path): - return load_pickled_dataframes(results_folder, 0, 0, "tlo.methods.healthsystem.summary")[ - "tlo.methods.healthsystem.summary" - ]["hsi_event_details"]["hsi_event_key_to_event_details"][0] +def get_hsi_event_keys_all_runs(results_folder: Path) -> pd.DataFrame: + """Returned pd.DataFrame gives the dictionaries of hsi_event_details for each draw and run. + NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" + def get_hsi_event_keys(_df): + """Get the hsi_event_keys for one particular run.""" + return _df['hsi_event_key_to_event_details'] -def create_equipment_catalogues(results_folder: Path, output_folder: Path): + return extract_results( + results_folder, + module='tlo.methods.healthsystem.summary', + key='hsi_event_details', + custom_generate_series=get_hsi_event_keys + ) - # Declare output file names - output_detailed_file_name = 'equipment_annual_counts__all_event_details.csv' - output_file_name = 'equipment_annual_counts__by_Date_EventName_FacLevel.csv' + +def create_equipment_catalogues(results_folder: Path, output_folder: Path): # %% Catalogue equipment by all HSI event details - sim_equipment = get_annual_hsi_event_counts(results_folder) + sim_equipment = get_monthly_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) - sim_equipment_df.fillna(0, inplace=True) - - hsi_event_keys = get_hsi_event_keys(results_folder) - - decoded_keys = sim_equipment_df.index.get_level_values(1).astype(str).map(hsi_event_keys) - sim_equipment_df = pd.concat([sim_equipment_df, pd.DataFrame(decoded_keys.tolist(), index=sim_equipment_df.index)], axis=1) - # Make values in 'appt_footprint', and 'beddays_footprint' columns to be string - sim_equipment_df['appt_footprint'] = sim_equipment_df['appt_footprint'].apply(lambda x: ', '.join(map(str, x))) - sim_equipment_df['beddays_footprint'] = sim_equipment_df['beddays_footprint'].apply(lambda x: ', '.join(map(str, x))) - # Explode the 'equipment' column - exploded_df = sim_equipment_df.explode('equipment') - # Remove the 'event_details_key' and replace the index with hsi event details as indexes - exploded_df = exploded_df.droplevel(level=1) - exploded_df = exploded_df.set_index( - ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', - 'equipment'], append=True - ) - # Sum values with the same multi-index - exploded_df = exploded_df.groupby(exploded_df.index).sum() - # Convert the index back to a MultiIndex - exploded_df.index = pd.MultiIndex.from_tuples(exploded_df.index) - exploded_df.index.names = \ - ['date', 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', - 'equipment'] - - # Save the detailed equipment catalogue by levels - exploded_df.to_csv(output_folder / output_detailed_file_name) + hsi_event_keys = get_hsi_event_keys_all_runs(results_folder) + + final_df = pd.DataFrame() + + def details_col_to_str(details_col): + return details_col.apply(lambda x: ', '.join(map(str, x))) + + for col in hsi_event_keys.columns: + df_col = sim_equipment_df[col].dropna() + decoded_keys = df_col.index.get_level_values(1).astype(str).map(hsi_event_keys.at[0, col]) + + # %%% Verify the keys in dictionary and dataframe for the run 'col' are same + # Check if all keys in hsi_event_keys_set are in the 'event_details_key' of df_col + hsi_event_keys_set = set(hsi_event_keys.at[0, col].keys()) + missing_keys_df =\ + [key for key in hsi_event_keys_set if key not in df_col.index.get_level_values('event_details_key')] + + # Check if all keys in the 'event_details_key' of df_col are in hsi_event_keys_set + missing_keys_dict =\ + [key for key in df_col.index.get_level_values('event_details_key') if key not in hsi_event_keys_set] + + # Warn if some keys are missing + if missing_keys_df: + warnings.warn(UserWarning(f"Keys missing in sim_equipment_df for the run {col}: {missing_keys_df}")) + + if missing_keys_dict: + warnings.warn(UserWarning(f"Keys missing in hsi_event_keys for the run {col}: {missing_keys_dict}")) + # %%% + + df_col = pd.concat([df_col, pd.DataFrame(decoded_keys.tolist(), index=df_col.index)], axis=1) + # Make values in 'appt_footprint', 'beddays_footprint' columns to be string + df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) + df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) + # Explode the 'equipment' column + exploded_df = df_col.explode('equipment') + # Remove the 'event_details_key' and replace the index with hsi event details as indexes + exploded_df = exploded_df.droplevel(level=1) + exploded_df = exploded_df.set_index( + ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', + 'equipment'], append=True + ) + # Sum values with the same multi-index (keep also empty indexes) + exploded_df = exploded_df.groupby(level=exploded_df.index.names, dropna=False).sum() + # Add the results for the run 'col' to final_df + final_df = pd.concat([final_df, exploded_df], axis=1) + + # Replace NaN with 0 + final_df.fillna(0, inplace=True) + # Save the detailed equipment catalogue + final_df.to_csv(output_folder / output_detailed_file_name) print(f'{output_detailed_file_name} saved.') # --- - # %% Catalogue equipment by Facility Levels and HSI Event Names - - equipment_counts_by_date_hsi_name_level_df = exploded_df.copy() - - # Sum counts for each equipment with the same date, hsi event name, and level (remaining indexes removed) - # equipment_counts_by_date_hsi_name_level_df - equipment_counts_by_date_hsi_name_level_df = \ - equipment_counts_by_date_hsi_name_level_df.groupby(['date', 'event_name', 'facility_level', 'equipment']).sum() - - # Save the CSV equipment counts - equipment_counts_by_date_hsi_name_level_df.to_csv(output_folder / output_file_name) + # %% Catalogue equipment by Treatment ID and Facility Levels + equipment_counts_by_date_treatment_id_level_df = final_df.copy() + + # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), + # keeping only non-empty 'equipment' indexes + equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( + ['date', 'treatment_id', 'facility_level', 'equipment'], + dropna=True + # TODO: make 'treatment_id', 'facility_level' to be an input + ).sum() + + # Sum counts annually + equipment_counts_by_date_treatment_id_level_df['year'] = \ + equipment_counts_by_date_treatment_id_level_df.index.get_level_values('date').year + # TODO: make annual/monthly results according to input + equipment_counts_by_date_treatment_id_level_df.set_index('year', append=True, inplace=True) + equipment_counts_by_date_treatment_id_level_df.index.droplevel('date') + equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( + ['year', 'treatment_id', 'facility_level', 'equipment'] + ).sum() + + # Save the equipment counts CSV + equipment_counts_by_date_treatment_id_level_df.to_csv(output_folder / output_file_name) print(f'{output_file_name} saved.') - # --- + # # --- return 0 From e823c1f1546d20858801fca3261307da04a67461 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 00:11:13 +0000 Subject: [PATCH 168/443] equip_catalogue: input hsi event details by which to catalog equipment --- .../equipment/equipment_catalogue.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 1ba0f83cb4..89d0c804c7 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -6,13 +6,23 @@ from tlo.analysis.utils import extract_results -# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Declare whether to scale the counts to Malawi population size +# (True/False) do_scaling = True -# Declare output file names +# Declare as list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) +# (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') +catalog_by = ['treatment_id', 'facility_level'] +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +# %%% Output file names +# detailed CSV name output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' -output_file_name = 'equipment_annual_counts__by_Year_TreatmentID_FacLevel.csv' -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# requested details only CSV name +output_file_name_prefix = 'equipment_annual_counts__by_year_' +output_file_name_suffix = '.csv' +output_file_name_details_specified = '_'.join(catalog_by) +output_file_name = output_file_name_prefix + output_file_name_details_specified + output_file_name_suffix def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: @@ -78,7 +88,7 @@ def get_hsi_event_keys(_df): def create_equipment_catalogues(results_folder: Path, output_folder: Path): - # %% Catalogue equipment by all HSI event details + # %% Catalog equipment by all HSI event details sim_equipment = get_monthly_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) hsi_event_keys = get_hsi_event_keys_all_runs(results_folder) @@ -134,15 +144,15 @@ def details_col_to_str(details_col): print(f'{output_detailed_file_name} saved.') # --- - # %% Catalogue equipment by Treatment ID and Facility Levels + # %% Catalog equipment by requested details equipment_counts_by_date_treatment_id_level_df = final_df.copy() # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), # keeping only non-empty 'equipment' indexes + to_be_grouped_by = ['date'] + catalog_by + ['equipment'] equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( - ['date', 'treatment_id', 'facility_level', 'equipment'], + to_be_grouped_by, dropna=True - # TODO: make 'treatment_id', 'facility_level' to be an input ).sum() # Sum counts annually From 39ec0872d43b3533e10eb67935c08a11b020b32a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 00:41:54 +0000 Subject: [PATCH 169/443] equip_catalogue: input time period by which to catalog equipment --- .../equipment/equipment_catalogue.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 89d0c804c7..da62e30afd 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -10,19 +10,22 @@ # Declare whether to scale the counts to Malawi population size # (True/False) do_scaling = True -# Declare as list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) +# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) # (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') -catalog_by = ['treatment_id', 'facility_level'] +catalog_by_details = ['treatment_id', 'facility_level'] +# Declare which time period you want the equipment be grouped in the catalogue (choose only one) +# (periods: 'monthly', 'annual') +catalog_by_time = 'annual' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# TODO: verify inputs are as expected # %%% Output file names # detailed CSV name output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' # requested details only CSV name -output_file_name_prefix = 'equipment_annual_counts__by_year_' -output_file_name_suffix = '.csv' -output_file_name_details_specified = '_'.join(catalog_by) -output_file_name = output_file_name_prefix + output_file_name_details_specified + output_file_name_suffix +time_index = 'year' if catalog_by_time == 'annual' else 'date' +output_file_name = \ + 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: @@ -145,28 +148,29 @@ def details_col_to_str(details_col): # --- # %% Catalog equipment by requested details - equipment_counts_by_date_treatment_id_level_df = final_df.copy() + equipment_counts_by_time_and_requested_details = final_df.copy() # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), # keeping only non-empty 'equipment' indexes - to_be_grouped_by = ['date'] + catalog_by + ['equipment'] - equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( + to_be_grouped_by = ['date'] + catalog_by_details + ['equipment'] + equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( to_be_grouped_by, dropna=True ).sum() - # Sum counts annually - equipment_counts_by_date_treatment_id_level_df['year'] = \ - equipment_counts_by_date_treatment_id_level_df.index.get_level_values('date').year - # TODO: make annual/monthly results according to input - equipment_counts_by_date_treatment_id_level_df.set_index('year', append=True, inplace=True) - equipment_counts_by_date_treatment_id_level_df.index.droplevel('date') - equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( - ['year', 'treatment_id', 'facility_level', 'equipment'] - ).sum() + if catalog_by_time == 'annual': + # Sum counts annually + equipment_counts_by_time_and_requested_details['year'] = \ + equipment_counts_by_time_and_requested_details.index.get_level_values('date').year + equipment_counts_by_time_and_requested_details.set_index('year', append=True, inplace=True) + equipment_counts_by_time_and_requested_details.index.droplevel('date') + to_be_grouped_by = ['year'] + catalog_by_details + ['equipment'] + equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( + to_be_grouped_by + ).sum() # Save the equipment counts CSV - equipment_counts_by_date_treatment_id_level_df.to_csv(output_folder / output_file_name) + equipment_counts_by_time_and_requested_details.to_csv(output_folder / output_file_name) print(f'{output_file_name} saved.') # # --- From 1e75813a26403bdaf883364f551d471a0130f6b5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 00:48:08 +0000 Subject: [PATCH 170/443] equip_catalogue: list of requested details can be empty --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index da62e30afd..d69bc9e638 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -10,7 +10,7 @@ # Declare whether to scale the counts to Malawi population size # (True/False) do_scaling = True -# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) +# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose any number) # (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') catalog_by_details = ['treatment_id', 'facility_level'] # Declare which time period you want the equipment be grouped in the catalogue (choose only one) From 81f1e7e87fa2f4650b2bac47e6c877ae51fea5d9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 01:44:39 +0000 Subject: [PATCH 171/443] equip_catalogue: verify inputs as expected & set output file names in 'create_equipment_catalogues' fnc --- .../equipment/equipment_catalogue.py | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index d69bc9e638..66fcff01ae 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -17,15 +17,6 @@ # (periods: 'monthly', 'annual') catalog_by_time = 'annual' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# TODO: verify inputs are as expected - -# %%% Output file names -# detailed CSV name -output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' -# requested details only CSV name -time_index = 'year' if catalog_by_time == 'annual' else 'date' -output_file_name = \ - 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: @@ -90,6 +81,27 @@ def get_hsi_event_keys(_df): def create_equipment_catalogues(results_folder: Path, output_folder: Path): + # %%% Verify inputs are as expected + assert isinstance(do_scaling, bool), "The input parameter 'do_scaling' must be a boolean (True or False)" + assert isinstance(catalog_by_details, list), "The input parameter 'catalog_by_details' must be a list" + event_details = \ + {'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint'} + for item in catalog_by_details: + assert isinstance(item, str) and item in event_details, \ + f"Each element in the input list 'catalog_by_details' must be a string and be one of the details:\n" \ + f"{event_details}" + assert catalog_by_time in {'monthly', 'annual'}, \ + "The input parameter 'catalog_by_time' must be one of the strings ('monthly' or 'annual')" + # --- + + # %%% Set output file names + # detailed CSV name + output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' + # requested details only CSV name + time_index = 'year' if catalog_by_time == 'annual' else 'date' + output_file_name = \ + 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' + # --- # %% Catalog equipment by all HSI event details sim_equipment = get_monthly_hsi_event_counts(results_folder) @@ -172,7 +184,7 @@ def details_col_to_str(details_col): # Save the equipment counts CSV equipment_counts_by_time_and_requested_details.to_csv(output_folder / output_file_name) print(f'{output_file_name} saved.') - # # --- + # --- return 0 From 03e3f09fdcbaf3a33c039798054e804d44ab2ce8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:15:44 +0100 Subject: [PATCH 172/443] brc, co: used_equipment renamed to EQUIPMENT; if equip always same for HSI - set in __init__, otherwise updated in apply diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 2529911d8..f19ec5358 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -647,6 +647,8 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. # TODO: but the appt footprints suggests mammography to be provided + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -760,6 +762,10 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 88736c98c..35db50580 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1255,7 +1255,8 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) 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 record 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: @@ -1279,6 +1280,14 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) ) _new_contraceptive = self.new_contraceptive + + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher + if _new_contraceptive == 'female_sterilization': + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + elif _new_contraceptive == 'IUD': + self.EQUIPMENT.update({'Equipment used when performing IUD'}) + else: _new_contraceptive = "not_using" --- src/tlo/methods/breast_cancer.py | 6 ++++++ src/tlo/methods/contraception.py | 11 ++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 2529911d84..f19ec5358e 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -647,6 +647,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. # TODO: but the appt footprints suggests mammography to be provided + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -760,6 +762,10 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 88736c98cb..35db505805 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1255,7 +1255,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 record 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: @@ -1279,6 +1280,14 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher + if _new_contraceptive == 'female_sterilization': + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + elif _new_contraceptive == 'IUD': + self.EQUIPMENT.update({'Equipment used when performing IUD'}) + else: _new_contraceptive = "not_using" From 2317e180a6d19edd14eff659692e66854d74e66c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 18 Sep 2023 17:33:16 +0100 Subject: [PATCH 173/443] brc: comment updated diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index f19ec5358..531e9ecfd 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -648,7 +648,7 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. # TODO: but the appt footprints suggests mammography to be provided self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI + 'Mammography maybe?'} # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index f19ec5358e..531e9ecfd0 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -648,7 +648,7 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. # TODO: but the appt footprints suggests mammography to be provided self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammography maybe?'} # biopsy and ?mammography always performed with this HSI + 'Mammography maybe?'} # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props From eaa79a4654b4d5d21f75f2afafdbdac9b16c9f1d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 00:00:00 +0100 Subject: [PATCH 174/443] brc: comment updated diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index aef476c87..c615eb365 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -763,7 +763,7 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): # Update equipment used with treatment # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher - self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) + self.EQUIPMENT.update({'Anything used for mastectomy'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 531e9ecfd0..a7f7b13aa2 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -764,7 +764,7 @@ def apply(self, person_id, squeeze_factor): # Update equipment used with treatment # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher - self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) + self.EQUIPMENT.update({'Anything used for mastectomy'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( From 0f36b614ea88d82526b94af216b031fa8fda82f5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 19:02:17 +0100 Subject: [PATCH 175/443] co: comment updated diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 2854f36a2..64dba7bff 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1253,7 +1253,7 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) 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 and record used equipment if any, if not set the contraception to + # 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) --- src/tlo/methods/contraception.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 35db505805..0d36948599 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1255,7 +1255,7 @@ 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 and record used equipment if any, if not set the contraception to + # 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) From daf95ae55a52a7635557f15c6ccc4ac6c3bd148f Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:11:12 +0100 Subject: [PATCH 176/443] breast_cancer: dummy used_equipment added where Andrew requested diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index c615eb365..2c2924758 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -668,6 +668,8 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', --- src/tlo/methods/breast_cancer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index a7f7b13aa2..0bf8d4cdd6 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -669,6 +669,8 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', From c165a1046ba4563c0c45897caae7884b3953fc0b Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:15:40 +0100 Subject: [PATCH 177/443] co: rm comment diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 1c1d3ea2e..bcd5b4bbc 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1282,7 +1282,6 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) _new_contraceptive = self.new_contraceptive # Update equipment when needed - # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': --- src/tlo/methods/contraception.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 0d36948599..bcd5b4bbc4 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1282,7 +1282,6 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive # Update equipment when needed - # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': From 77f35f0bc2022df62e2f7a12cdaced46aea25d73 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:24:02 +0100 Subject: [PATCH 178/443] hs: diff --git src/tlo/methods/healthsystem.py src/tlo/methods/healthsystem.py index 6f2f8d95e..2226d5945 100644 --- src/tlo/methods/healthsystem.py +++ src/tlo/methods/healthsystem.py --- src/tlo/methods/healthsystem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 6a34137f71..7446d017ab 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1743,7 +1743,6 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( From a633f2fb58bc3e5594f9480896dfddab89486e98 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:15:44 +0100 Subject: [PATCH 179/443] hs: equipment parameter added for HealthSystem module diff --git src/tlo/methods/healthsystem.py src/tlo/methods/healthsystem.py index 2226d5945..6f2f8d95e 100644 --- src/tlo/methods/healthsystem.py +++ src/tlo/methods/healthsystem.py @@ -1741,6 +1741,7 @@ class HealthSystem(Module): squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( --- src/tlo/methods/healthsystem.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 7446d017ab..6a34137f71 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1743,6 +1743,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( From d9b9dc649b3b2ba51a246695e0532858c8592385 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:44:03 +0100 Subject: [PATCH 180/443] co: rm the dummy examples of equipment diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index bcd5b4bbc..1143f42bb 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1257,6 +1257,7 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) # Determine whether the contraception is administrated (ie all essential items are available), # if so do log the availability of all items and update used equipment if any, if not set the contraception to # "not_using": + # if so do log the availability of all items, 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: @@ -1281,12 +1282,6 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) _new_contraceptive = self.new_contraceptive - # Update equipment when needed - if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) - elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Equipment used when performing IUD'}) - else: _new_contraceptive = "not_using" --- src/tlo/methods/contraception.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index bcd5b4bbc4..1143f42bbf 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1257,6 +1257,7 @@ def apply(self, person_id, squeeze_factor): # Determine whether the contraception is administrated (ie all essential items are available), # if so do log the availability of all items and update used equipment if any, if not set the contraception to # "not_using": + # if so do log the availability of all items, 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: @@ -1281,12 +1282,6 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Update equipment when needed - if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) - elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Equipment used when performing IUD'}) - else: _new_contraceptive = "not_using" From 8e9e7f2bf16810f87dc09a4170afc8be482b3f55 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 00:00:00 +0100 Subject: [PATCH 181/443] brc: used_equipment rm diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 0bf8d4cdd..a7f7b13aa 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -669,8 +669,6 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', --- src/tlo/methods/breast_cancer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 0bf8d4cdd6..a7f7b13aa2 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -669,8 +669,6 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', From 2c46098038513fe029dfb14cb7a3ffbdee8ca8e9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 19:02:17 +0100 Subject: [PATCH 182/443] co: dummy examples of equip added, redundant comment rm diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 1143f42bb..0d3694859 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1257,7 +1257,6 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) # Determine whether the contraception is administrated (ie all essential items are available), # if so do log the availability of all items and update used equipment if any, if not set the contraception to # "not_using": - # if so do log the availability of all items, 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: @@ -1282,6 +1281,13 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) _new_contraceptive = self.new_contraceptive + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher + if _new_contraceptive == 'female_sterilization': + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + elif _new_contraceptive == 'IUD': + self.EQUIPMENT.update({'Equipment used when performing IUD'}) + else: _new_contraceptive = "not_using" --- src/tlo/methods/contraception.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 1143f42bbf..0d36948599 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1257,7 +1257,6 @@ def apply(self, person_id, squeeze_factor): # Determine whether the contraception is administrated (ie all essential items are available), # if so do log the availability of all items and update used equipment if any, if not set the contraception to # "not_using": - # if so do log the availability of all items, 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: @@ -1282,6 +1281,13 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher + if _new_contraceptive == 'female_sterilization': + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + elif _new_contraceptive == 'IUD': + self.EQUIPMENT.update({'Equipment used when performing IUD'}) + else: _new_contraceptive = "not_using" From c767b5f816a0ea10474713c42a2de041084a9b89 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 12:41:29 +0100 Subject: [PATCH 183/443] adding equipment for routine ANC --- .../methods/care_of_women_during_pregnancy.py | 60 +++++++++++++++---- 1 file changed, 49 insertions(+), 11 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index b710de5ca1..2b06e9c5c5 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -748,6 +748,7 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # We use a temporary variable to store if proteinuria is detected proteinuria_diagnosed = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'dipstick'}) + hsi_event.EQUIPMENT.update({'Urine dip Stick'}) # The process is repeated for blood pressure monitoring if self.rng.random_sample() < params['prob_intervention_delivered_bp']: @@ -756,6 +757,7 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): hsi_event=hsi_event): hypertension_diagnosed = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'bp_measurement'}) + hsi_event.EQUIPMENT.update({'Sphygmomanometer'}) if not df.at[person_id, 'ac_gest_htn_on_treatment'] and\ (df.at[person_id, 'ps_htn_disorders'] != 'none') and pd.isnull(mni[person_id]['hypertension' @@ -921,6 +923,7 @@ def point_of_care_hb_testing(self, hsi_event): logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'hb_screen'}) hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_equipment']) + hsi_event.EQUIPMENT.update({'Haemoglobinometer'}) # We run the test through the dx_manager and if a woman has anaemia and its detected she will be admitted # for further care @@ -1074,20 +1077,25 @@ 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( + if avail: + + # TODO: this actually might be a fasting blood glucose test (not using glucometer) + hsi_event.EQUIPMENT.update({'Glucometer'}) + + if self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run='blood_test_glucose', hsi_event=hsi_event): - logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'gdm_screen'}) - mni[person_id]['anc_ints'].append('gdm_screen') + 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 @@ -1450,6 +1458,11 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + # Update equipment used during first ANC visit not directly related to interventions + self.EQUIPMENT.update( + {'Weighing scale', 'Height Pole (Stadiometer)', 'MUAC tape', 'Measuring tapes', + '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) @@ -1468,6 +1481,8 @@ 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.EQUIPMENT.update({'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) @@ -1536,7 +1551,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 + # Update equipment used during ANC visit not directly related to interventions + self.EQUIPMENT.update( + {'Weighing scale', 'Measuring tapes', + '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) @@ -1624,6 +1644,8 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1700,6 +1722,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1772,6 +1797,10 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update( + {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' + '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) @@ -1843,6 +1872,9 @@ def apply(self, person_id, squeeze_factor): gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'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: @@ -1906,6 +1938,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1963,6 +1998,9 @@ 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.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) + self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) if df.at[person_id, 'ac_to_be_admitted']: From 4ab5ff5b4c32bb0cc45c88b57b9ad39f85c6deb6 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 13:19:33 +0100 Subject: [PATCH 184/443] adding equipment for routine ANC - ultrasound --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 2b06e9c5c5..9fbe7251c3 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1553,7 +1553,7 @@ def apply(self, person_id, squeeze_factor): # =================================== INTERVENTIONS ==================================================== # Update equipment used during ANC visit not directly related to interventions self.EQUIPMENT.update( - {'Weighing scale', 'Measuring tapes', + {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' '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 From eb3dcb9e93859b46e15a7ba73b77c5a7583d3b12 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 14:47:34 +0100 Subject: [PATCH 185/443] adding equipment for emergency ANC, PAC and ectopic pregnancy --- src/tlo/methods/care_of_women_during_pregnancy.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 9fbe7251c3..920a33681c 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1218,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.EQUIPMENT.update({'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) @@ -1258,6 +1259,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.EQUIPMENT.update({'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(): @@ -1302,6 +1305,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.EQUIPMENT.update({'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 @@ -1339,6 +1343,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.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) def antibiotics_for_prom(self, individual_id, hsi_event): """ @@ -1360,6 +1365,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.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) def ectopic_pregnancy_treatment_doesnt_run(self, hsi_event): """ @@ -2040,6 +2046,9 @@ def __init__(self, module, person_id, visit_number): self.ACCEPTED_FACILITY_LEVEL = '1a' def apply(self, person_id, squeeze_factor): + + # TODO: n.b. equipment not added for this HSI but i think it will be deleted with the next PR + df = self.sim.population.props mother = df.loc[person_id] mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -2602,6 +2611,9 @@ def apply(self, person_id, squeeze_factor): self.module, self, self.module.item_codes_preg_consumables, core='post_abortion_care_core', optional='post_abortion_care_optional') + # TODO: equipment set for dilation and cutterage? oxygen? + self.EQUIPMENT.update({'Manual Vacuum aspiration Set', 'Drip stand', 'Infusion pump'}) + # Check HCW availability to deliver surgical removal of retained products sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], sf='retained_prod', @@ -2687,6 +2699,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.EQUIPMENT.update({'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 From f35d902d2425cada75362e7447504dc0f0b1f65c Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 14:57:07 +0100 Subject: [PATCH 186/443] linting --- src/tlo/methods/care_of_women_during_pregnancy.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 920a33681c..58b711b4f3 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1650,7 +1650,8 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) + self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + 'Stethoscope, foetal, monaural, Pinard, plastic'}) 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) From 0e44a3c83a471f1a304c3ef52a44a92f8be8810c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 12:11:31 +0100 Subject: [PATCH 187/443] rti: ntg diff --git src/tlo/methods/rti.py src/tlo/methods/rti.py index f92d751e1..f5dbeacdf 100644 --- src/tlo/methods/rti.py +++ src/tlo/methods/rti.py --- src/tlo/methods/rti.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index f92d751e14..f5dbeacdf5 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4664,7 +4664,9 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # consumables used by surgeon, gloves and facemask + # equipment used by surgeon, gloves and facemask + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From 8419796bd00f72e7183cafddacb68e95af54c37e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:10:01 +0100 Subject: [PATCH 188/443] rti: unified use of consumables/equipment terms --- src/tlo/methods/rti.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index f5dbeacdf5..f92d751e14 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4664,9 +4664,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From fce6d34c19d6658339deb7cc6ecfe5169c449714 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:44:03 +0100 Subject: [PATCH 189/443] co: rm the dummy examples of equipment diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 0d3694859..c9969916b 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1255,8 +1255,7 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) 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 and update used equipment if any, if not set the contraception to - # "not_using": + # if so do log the availability of all items, 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: @@ -1281,13 +1280,6 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) _new_contraceptive = self.new_contraceptive - # Update equipment when needed - # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher - if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) - elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Equipment used when performing IUD'}) - else: _new_contraceptive = "not_using" --- src/tlo/methods/contraception.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 0d36948599..c9969916b2 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1255,8 +1255,7 @@ 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 and update used equipment if any, if not set the contraception to - # "not_using": + # if so do log the availability of all items, 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: @@ -1281,13 +1280,6 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Update equipment when needed - # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher - if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) - elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Equipment used when performing IUD'}) - else: _new_contraceptive = "not_using" From 15bfe1f462426ba244c626eb124e54466661e8d2 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:15:44 +0100 Subject: [PATCH 190/443] brc, co: dummy examples updated/added diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index a7f7b13aa..d7a09c280 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -762,8 +762,7 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Update equipment used with treatment - # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + # Update equipment self.EQUIPMENT.update({'Anything used for mastectomy'}) # Schedule a post-treatment check for 12 months: diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index c9969916b..da1e2f3ba 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1280,6 +1280,12 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) _new_contraceptive = self.new_contraceptive + # Update equipment + if _new_contraceptive == 'female_sterilization': + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + elif _new_contraceptive == 'IUD': + self.EQUIPMENT.update({'Equipment used when performing IUD'}) + else: _new_contraceptive = "not_using" --- src/tlo/methods/breast_cancer.py | 3 +-- src/tlo/methods/contraception.py | 6 ++++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index a7f7b13aa2..d7a09c2801 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -762,8 +762,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Update equipment used with treatment - # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + # Update equipment self.EQUIPMENT.update({'Anything used for mastectomy'}) # Schedule a post-treatment check for 12 months: diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index c9969916b2..da1e2f3ba8 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1280,6 +1280,12 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive + # Update equipment + if _new_contraceptive == 'female_sterilization': + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + elif _new_contraceptive == 'IUD': + self.EQUIPMENT.update({'Equipment used when performing IUD'}) + else: _new_contraceptive = "not_using" From e3ba94df3bba4d7c769fddaf3a7eace16ca4ea52 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 00:00:00 +0100 Subject: [PATCH 191/443] brc: equip item updated diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index d7a09c280..7eabc5362 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -647,8 +647,7 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. # TODO: but the appt footprints suggests mammography to be provided - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammography maybe?'} # biopsy always performed with this HSI, hence always used the same set of equipment + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props --- src/tlo/methods/breast_cancer.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index d7a09c2801..7eabc5362e 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -647,8 +647,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. # TODO: but the appt footprints suggests mammography to be provided - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammography maybe?'} # biopsy always performed with this HSI, hence always used the same set of equipment + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 415c7c5938fbdc206440c31cca725f31fb696412 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 19:02:17 +0100 Subject: [PATCH 192/443] co: dummy examples of equip added diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 799e02f42..c2ac8af15 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1267,7 +1267,8 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) 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: --- src/tlo/methods/contraception.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index da1e2f3ba8..508cfefe19 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1255,7 +1255,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: From c1e38f5b8f263991dbdb499f7c4496ee89df918a Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 29 Sep 2023 12:41:29 +0100 Subject: [PATCH 193/443] ntg --- src/tlo/methods/care_of_women_during_pregnancy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 58b711b4f3..e4f04fc41f 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -574,7 +574,7 @@ def determine_gestational_age_for_next_contact(self, person_id): def antenatal_care_scheduler(self, individual_id, visit_to_be_scheduled, recommended_gestation_next_anc): """ - This function is responsible for scheduling a woman's next ANC contact in the schedule if she chooses to seek + This function is responsible for scheduling a womans next ANC contact in the schedule if she chooses to seek care again. It is called by each of the ANC HSIs. :param individual_id: individual_id :param visit_to_be_scheduled: Number if next visit in the schedule (2-8) @@ -1572,8 +1572,8 @@ def apply(self, person_id, squeeze_factor): self.module.antenatal_care_scheduler(person_id, visit_to_be_scheduled=3, recommended_gestation_next_anc=gest_age_next_contact) - # Then we administer interventions that are due to be delivered at this woman's gestational age, which may - # be in addition to intervention delivered in ANC2 + # Then we administer interventions that are due to be delivered at this womans gestational age, which may be + # in addition to intervention delivered in ANC2 if mother.ps_gestational_age_in_weeks < 26: self.module.albendazole_administration(hsi_event=self) self.module.iptp_administration(hsi_event=self) @@ -1621,7 +1621,7 @@ class HSI_CareOfWomenDuringPregnancy_ThirdAntenatalCareContact(HSI_Event, Indivi antenatal care contact (ANC3). It is scheduled by the HSI_CareOfWomenDuringPregnancy_SecondAntenatalCareContact for women who choose to seek additional ANC after their previous visit. It is recommended that this visit occur at 26 weeks gestation. This event delivers the interventions to women which are part of ANC3. Additionally interventions - that should be delivered according to a woman's gestational age and position in her ANC schedule are delivered. + that should be delivered according to a womans gestational age and position in her ANC schedule are delivered. Finally scheduling the next ANC contact in the occurs during this HSI along with admission to antenatal inpatient ward in the case of complications""" From f7a84e85fa74dabca00cb71b2e7d6e40ee39be66 Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 10:22:01 +0100 Subject: [PATCH 194/443] added some comments on equipment I think needed diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 7eabc5362..f0ecb3f40 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -647,7 +647,14 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. # TODO: but the appt footprints suggests mammography to be provided - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} # biopsy always performed with this HSI, hence always used the same set of equipment + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + + # ap_oct23 - Eva, I cannot locate a biopsy needle in the equipment - perhaps it is in consumables + # the other equipment need is for histology in the lab - there is a whole long list of items needed + # for histology done on the sample in the lab - do we need to add each of these, or can we have a + # package ? I do not think mammography is done at this point but I could be wrong. + + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -726,6 +733,9 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + # ap_oct23 - I believe this will almost always be mastectomy surgery with chemotherapy, so I think for equipment + # we just need the standard surgery equipment list. We may need to add radiotherapy when more available. + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -791,6 +801,10 @@ class HSI_BreastCancer_PostTreatmentCheck(HSI_Event, IndividualScopeEventMixin): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # ap_oct23 - Eva, I'm not aware of any equipment needed here. Clinical guidelines do not specify what + # checks or monitoring are indicated + + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -847,6 +861,9 @@ class HSI_BreastCancer_PalliativeCare(HSI_Event, IndividualScopeEventMixin): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # not sure there is any need for equipment here + + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] --- src/tlo/methods/breast_cancer.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 7eabc5362e..f0ecb3f404 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -647,7 +647,14 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. # TODO: but the appt footprints suggests mammography to be provided - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} # biopsy always performed with this HSI, hence always used the same set of equipment + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + + # ap_oct23 - Eva, I cannot locate a biopsy needle in the equipment - perhaps it is in consumables + # the other equipment need is for histology in the lab - there is a whole long list of items needed + # for histology done on the sample in the lab - do we need to add each of these, or can we have a + # package ? I do not think mammography is done at this point but I could be wrong. + + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -726,6 +733,9 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + # ap_oct23 - I believe this will almost always be mastectomy surgery with chemotherapy, so I think for equipment + # we just need the standard surgery equipment list. We may need to add radiotherapy when more available. + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -791,6 +801,10 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # ap_oct23 - Eva, I'm not aware of any equipment needed here. Clinical guidelines do not specify what + # checks or monitoring are indicated + + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -847,6 +861,9 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # not sure there is any need for equipment here + + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From 0fb71f3b25e20094502da838fdc1558df5de4a5f Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 10:34:54 +0100 Subject: [PATCH 195/443] added some comments on equipment I think needed --- src/tlo/methods/oesophagealcancer.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index fb8e96116c..e8714176ec 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -644,6 +644,9 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # I think this will need endoscope and biopsy needle. Also lab equipment needed to perform histology. + # I can't see endoscope in equipment list but it may be given a slightly different name + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -713,6 +716,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + # equipment need here will be surgery + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -771,6 +776,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # equipment: I assume endoscope needed for this + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -827,6 +834,10 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # when radiology available then palliative radiology may be performed but suggest we don't need to include yet + # not sure what equipment needed for Endoscopic stent placement or Feeding tube which are done as palliative + # measures + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From e26d371eea50b86e0531ae15a44236af07b596a7 Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 10:52:39 +0100 Subject: [PATCH 196/443] added some comments on equipment I think needed --- src/tlo/methods/prostate_cancer.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 255c03a525..95425eba9f 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -671,6 +671,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -716,6 +718,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -761,6 +765,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -830,6 +836,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + # equipment as required for surgery + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -889,6 +897,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # possibly biopsy and histology + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -945,6 +955,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # generally not sure equipment is required as therapy is with drug, but can require castration surgery + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From da2df5d4991410e008abe97044f12e1331ecee63 Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:03:30 +0100 Subject: [PATCH 197/443] added some comments on equipment I think needed --- src/tlo/methods/bladder_cancer.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 7231125519..300c7d486f 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -658,6 +658,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # equipment: (ultrsound guided) biopsy, lab equipment for histology + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -721,6 +723,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # equipment: (ultrsound guided) biopsy, lab equipment for histology + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -789,6 +793,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) + # equipment: standard equipment for surgery + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -848,6 +854,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # I assume ultrasound (Ultrasound scanning machine) and biopsy + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -904,6 +912,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # no equipment as far as I am aware + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From 569d5406cf4e9edea9f144997ef402667dc9ed95 Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:10:17 +0100 Subject: [PATCH 198/443] added some comments on equipment I think needed --- src/tlo/methods/other_adult_cancers.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 508e96c12b..ba0e60d6e9 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -635,6 +635,9 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # equipment: investigations will differ by presenting symptom, but suggest we have biopsy and histology + # and ultrasound + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -705,6 +708,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + # equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -765,6 +770,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + # equipment: some checks will involve further biopsy, ultrasound, histology + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -825,6 +832,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + # equipment: in general not required I don't think + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From e21b512070d8c24e5fd4d7de21eafaf52e6a63fe Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:32:53 +0100 Subject: [PATCH 199/443] added some comments on equipment I think needed --- src/tlo/methods/epilepsy.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tlo/methods/epilepsy.py b/src/tlo/methods/epilepsy.py index f06c62d098..55d6553106 100644 --- a/src/tlo/methods/epilepsy.py +++ b/src/tlo/methods/epilepsy.py @@ -591,6 +591,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # no equipment needed + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -622,6 +624,7 @@ def apply(self, person_id, squeeze_factor): tclose=None, priority=0) + # todo: may need to consider iv diazepam as another hsi class HSI_Epilepsy_Follow_Up(HSI_Event, IndividualScopeEventMixin): @@ -637,6 +640,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self._counter_of_failed_attempts_due_to_unavailable_medicines = 0 + # no equipment needed + def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] From 64aff9e7e23a0bdffe0a52a4c08d2cb466e61526 Mon Sep 17 00:00:00 2001 From: andrew-phillips-1 <39617310+andrew-phillips-1@users.noreply.github.com> Date: Sat, 30 Sep 2023 11:34:06 +0100 Subject: [PATCH 200/443] added some comments on equipment I think needed --- src/tlo/methods/depression.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/tlo/methods/depression.py b/src/tlo/methods/depression.py index 8f5fd9661c..dbbb90db21 100644 --- a/src/tlo/methods/depression.py +++ b/src/tlo/methods/depression.py @@ -787,6 +787,8 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self.num_of_sessions_had = 0 # A counter for the number of sessions of talking therapy had + # no equipment needed + def apply(self, person_id, squeeze_factor): """Set the property `de_ever_talk_ther` to be True and schedule the next session in the course if the person has not yet had 5 sessions.""" @@ -819,6 +821,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MentOPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + # no equipment needed + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -860,6 +864,8 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + # no equipment needed + def apply(self, person_id, squeeze_factor): df = self.sim.population.props From ad3998f58d3b2cec93dd08e0d5b369d986f51109 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 9 Oct 2023 17:22:13 +0100 Subject: [PATCH 201/443] PEP 8 --- src/tlo/methods/breast_cancer.py | 2 -- src/tlo/methods/epilepsy.py | 1 + src/tlo/methods/other_adult_cancers.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index f0ecb3f404..402266516c 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -804,7 +804,6 @@ def __init__(self, module, person_id): # ap_oct23 - Eva, I'm not aware of any equipment needed here. Clinical guidelines do not specify what # checks or monitoring are indicated - def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -863,7 +862,6 @@ def __init__(self, module, person_id): # not sure there is any need for equipment here - def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] diff --git a/src/tlo/methods/epilepsy.py b/src/tlo/methods/epilepsy.py index 55d6553106..067e53a006 100644 --- a/src/tlo/methods/epilepsy.py +++ b/src/tlo/methods/epilepsy.py @@ -626,6 +626,7 @@ def apply(self, person_id, squeeze_factor): # todo: may need to consider iv diazepam as another hsi + class HSI_Epilepsy_Follow_Up(HSI_Event, IndividualScopeEventMixin): def __init__(self, module, person_id): diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index ba0e60d6e9..1b5828af62 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -708,7 +708,7 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - # equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available + # equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 14de031f0db7fef960b0fcac5ded613766be4378 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 10 Oct 2023 10:15:28 +0100 Subject: [PATCH 202/443] co: empty equipment declaration --- src/tlo/methods/contraception.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 508cfefe19..907f780d67 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1194,6 +1194,7 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level + self.EQUIPMENT = set() # no specific equipment required unless changed in some circumstances @property def EXPECTED_APPT_FOOTPRINT(self): From 008c22107aeb4fd69db5f2c7531d4a21afe21f0a Mon Sep 17 00:00:00 2001 From: Tara <37845078+tdm32@users.noreply.github.com> Date: Wed, 25 Oct 2023 12:05:14 +0100 Subject: [PATCH 203/443] Equipment/integration in modules tara (#1146) * co: rm comment * ma: add equipment for malaria HSIs * hiv: add equipment for HIV HSIs * tb: add equipment for TB HSIs * linting * make equipment declaration conditional on appt actually occurring * add comments on equipment for xray * hv, ma & tb: finalise equipment induction style --------- Co-authored-by: Eva Janouskova --- src/tlo/methods/contraception.py | 2 +- src/tlo/methods/hiv.py | 11 ++++++++++- src/tlo/methods/malaria.py | 9 +++++++++ src/tlo/methods/tb.py | 30 ++++++++++++++++++++++++++++++ 4 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 907f780d67..6f9e4254b5 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1194,7 +1194,7 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level - self.EQUIPMENT = set() # no specific equipment required unless changed in some circumstances + self.EQUIPMENT = set() @property def EXPECTED_APPT_FOOTPRINT(self): diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index 781e9e064b..d3450c03d1 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -2098,6 +2098,7 @@ def __init__( self.TREATMENT_ID = "Hiv_Test" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"VCTNegative": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """Do the testing and referring to other services""" @@ -2235,6 +2236,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MaleCirc": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.number_of_occurrences = 0 + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """ Do the circumcision for this man. If he is already circumcised, this is a follow-up appointment.""" @@ -2255,6 +2257,10 @@ def apply(self, person_id, squeeze_factor): # Update circumcision state df.at[person_id, "li_is_circ"] = True + # Update equipment + self.EQUIPMENT.update({'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( @@ -2291,6 +2297,7 @@ def __init__(self, module, person_id, referred_from, repeat_visits): self.ACCEPTED_FACILITY_LEVEL = '1a' self.referred_from = referred_from self.repeat_visits = repeat_visits + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """ @@ -2359,6 +2366,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Hiv_Prevention_Prep" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"PharmDispensing": 1, "VCTNegative": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """Start PrEP for this person; or continue them on PrEP for 3 more months""" @@ -2420,6 +2428,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.counter_for_drugs_not_available = 0 self.counter_for_did_not_run = 0 + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """This is a Health System Interaction Event - start or continue HIV treatment for 6 more months""" @@ -2540,7 +2549,7 @@ def do_at_initiation(self, person_id): if drugs_available: # Assign person to be have suppressed or un-suppressed viral load # (If person is VL suppressed This will prevent the Onset of AIDS, or an AIDS death if AIDS has already - # onset,) + # onset) vl_status = self.determine_vl_status( age_of_person=person["age_years"] ) diff --git a/src/tlo/methods/malaria.py b/src/tlo/methods/malaria.py index daa52776c5..44d30bb3ca 100644 --- a/src/tlo/methods/malaria.py +++ b/src/tlo/methods/malaria.py @@ -753,6 +753,7 @@ def __init__(self, module, person_id, facility_level='1a'): 'Under5OPD' if person_age_years < 5 else 'Over5OPD': 1} ) self.ACCEPTED_FACILITY_LEVEL = '1a' if (self.facility_level == '1a') else '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -850,6 +851,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Malaria_Test' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ConWithDCSA': 1}) self.ACCEPTED_FACILITY_LEVEL = '0' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -902,6 +904,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -994,6 +997,7 @@ def __init__(self, module, person_id): ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -1017,6 +1021,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 + # Update equipment + self.EQUIPMENT.update({'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) @@ -1047,6 +1055,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Malaria_Prevention_Iptp' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 79afd6fa5f..4b61b67526 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -1721,6 +1721,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Screening" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """Do the screening and referring to next tests""" @@ -1814,6 +1815,9 @@ def apply(self, person_id, squeeze_factor): test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_clinical", hsi_event=self ) + if test_result is not None: + # Update equipment + self.EQUIPMENT.update({'Sputum Collection box', 'Ordinary Microscope'}) elif test == "xpert": ACTUAL_APPT_FOOTPRINT = self.make_appt_footprint( @@ -1829,12 +1833,16 @@ def apply(self, person_id, squeeze_factor): test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_xpert_test_smear_negative", hsi_event=self ) + if test_result is not None: + # Update equipment + self.EQUIPMENT.update({'Sputum Collection box', 'Gene Expert (16 Module)'}) # ------------------------- testing referrals ------------------------- # # if none of the tests are available, try again for sputum # requires another appointment - added in ACTUAL_APPT_FOOTPRINT if test_result is None: + if smear_status: test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_sputum_test_smear_positive", hsi_event=self @@ -1847,9 +1855,13 @@ def apply(self, person_id, squeeze_factor): ACTUAL_APPT_FOOTPRINT = self.make_appt_footprint( {"Over5OPD": 2, "LabTBMicro": 1} ) + if test_result is not None: + # Update equipment + self.EQUIPMENT.update({'Sputum Collection box', 'Ordinary Microscope'}) # if still no result available, rely on clinical diagnosis if test_result is None: + test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_clinical", hsi_event=self ) @@ -1956,6 +1968,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Clinical" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Under5OPD": 0.5}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """ Do the screening and referring process """ @@ -2023,6 +2036,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Xray" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"DiagRadio": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -2044,6 +2058,9 @@ 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: + # Update equipment + self.EQUIPMENT.update({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items # if consumables not available, refer to level 2 # return blank footprint as xray did not occur @@ -2094,6 +2111,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Xray" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"DiagRadio": 1}) self.ACCEPTED_FACILITY_LEVEL = '2' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -2115,6 +2133,9 @@ 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: + # Update equipment + self.EQUIPMENT.update({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items # if consumables not available, rely on clinical diagnosis # return blank footprint as xray was not available @@ -2162,6 +2183,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Treatment" self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() self.number_of_occurrences = 0 @property @@ -2310,6 +2332,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Test_FollowUp" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"TBFollowUp": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): p = self.module.parameters @@ -2373,6 +2396,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: + # Update equipment + self.EQUIPMENT.update({'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"]: @@ -2387,6 +2413,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: + # Update equipment + self.EQUIPMENT.update({'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"): @@ -2441,6 +2470,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Prevention_Ipt" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() self.number_of_occurrences = 0 def apply(self, person_id, squeeze_factor): From 6f12a5cdf10b764f43d70efdac80bc977d436b86 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 25 Oct 2023 12:09:41 +0100 Subject: [PATCH 204/443] tb: no additional lines --- src/tlo/methods/tb.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 4b61b67526..691412dc96 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -1842,7 +1842,6 @@ def apply(self, person_id, squeeze_factor): # if none of the tests are available, try again for sputum # requires another appointment - added in ACTUAL_APPT_FOOTPRINT if test_result is None: - if smear_status: test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_sputum_test_smear_positive", hsi_event=self @@ -1861,7 +1860,6 @@ def apply(self, person_id, squeeze_factor): # if still no result available, rely on clinical diagnosis if test_result is None: - test_result = self.sim.modules["HealthSystem"].dx_manager.run_dx_test( dx_tests_to_run="tb_clinical", hsi_event=self ) From 8321f053f892cbaac938a0f22a976ae71b75f893 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 25 Oct 2023 15:26:37 +0100 Subject: [PATCH 205/443] initial additions of equipment for delivery care --- src/tlo/methods/labour.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index d9b6118596..fa852b77bb 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1866,6 +1866,9 @@ def refer_for_cs(): sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='avd', hsi_event=hsi_event) + # log equipment + hsi_event.EQUIPMENT.update({'Delivery Forceps', 'Vacuum extractor'}) + if avail and sf_check: pregnancy_helper_functions.log_met_need(self, f'avd_{indication}', hsi_event) @@ -2891,6 +2894,11 @@ def apply(self, person_id, squeeze_factor): self.module, self, self.module.item_codes_lab_consumables, core='delivery_core', optional='delivery_optional') + # Log required equipment + self.EQUIPMENT.update({'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 From ddfeefe5c011733ef5327a36bab6af9384e63790 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 25 Oct 2023 18:49:13 +0100 Subject: [PATCH 206/443] ac: empty declaration of equipment --- src/tlo/methods/care_of_women_during_pregnancy.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index e4f04fc41f..68a43fc3b4 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1442,6 +1442,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AntenatalFirst': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1537,6 +1538,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1632,6 +1634,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1711,6 +1714,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1786,6 +1790,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1859,6 +1864,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1927,6 +1933,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1989,6 +1996,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2045,6 +2053,7 @@ def __init__(self, module, person_id, visit_number): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({('AntenatalFirst' if (self.visit_number == 1) else 'ANCSubsequent'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): @@ -2161,6 +2170,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2201,6 +2211,7 @@ def __init__(self, module, person_id): beddays = self.module.calculate_beddays(person_id) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': beddays}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2458,6 +2469,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.ALERT_OTHER_DISEASES = [] + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2507,6 +2519,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.ALERT_OTHER_DISEASES = [] + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2593,6 +2606,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1b' # any hospital? self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 3}) # todo: check with TC + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2683,6 +2697,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 5}) # todo: check with TC + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 3537f8efa497421c799be1b9712493bf7ab66fc1 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 26 Oct 2023 13:49:30 +0100 Subject: [PATCH 207/443] additions of equipment for PNC --- .../methods/care_of_women_during_pregnancy.py | 7 ++- src/tlo/methods/labour.py | 51 +++++++++++++++---- src/tlo/methods/newborn_outcomes.py | 7 +++ 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 68a43fc3b4..c7f5009c2b 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -735,6 +735,7 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # Delivery of the intervention is conditioned on a random draw against a probability that the intervention # would be delivered (used to calibrate to SPA data - acts as proxy for clinical quality) if self.rng.random_sample() < params['prob_intervention_delivered_urine_ds']: + hsi_event.EQUIPMENT.update({'Urine dip Stick'}) # check consumables avail = pregnancy_helper_functions.return_cons_avail( @@ -748,16 +749,15 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # We use a temporary variable to store if proteinuria is detected proteinuria_diagnosed = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'dipstick'}) - hsi_event.EQUIPMENT.update({'Urine dip Stick'}) # The process is repeated for blood pressure monitoring if self.rng.random_sample() < params['prob_intervention_delivered_bp']: + hsi_event.EQUIPMENT.update({'Sphygmomanometer'}) if self.sim.modules['HealthSystem'].dx_manager.run_dx_test(dx_tests_to_run='blood_pressure_measurement', hsi_event=hsi_event): hypertension_diagnosed = True logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'bp_measurement'}) - hsi_event.EQUIPMENT.update({'Sphygmomanometer'}) if not df.at[person_id, 'ac_gest_htn_on_treatment'] and\ (df.at[person_id, 'ps_htn_disorders'] != 'none') and pd.isnull(mni[person_id]['hypertension' @@ -2626,8 +2626,7 @@ def apply(self, person_id, squeeze_factor): self.module, self, self.module.item_codes_preg_consumables, core='post_abortion_care_core', optional='post_abortion_care_optional') - # TODO: equipment set for dilation and cutterage? oxygen? - self.EQUIPMENT.update({'Manual Vacuum aspiration Set', 'Drip stand', 'Infusion pump'}) + self.EQUIPMENT.update({'D&C set', 'Suction Curettage machine', 'Drip stand', 'Infusion pump'}) # Check HCW availability to deliver surgical removal of retained products sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index fa852b77bb..0c55d10f9b 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -2118,17 +2118,29 @@ def surgical_management_of_pph(self, hsi_event): if not mni[person_id]['retained_placenta']: - # We apply a probability that surgical techniques will be effective - treatment_success_pph = params['success_rate_pph_surgery'] > self.rng.random_sample() + if avail and sf_check: + + # Log equipment + # Todo: link to surgical equipment package when that exsists + hsi_event.EQUIPMENT.update( + {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) + + # We apply a probability that surgical techniques will be effective + treatment_success_pph = params['success_rate_pph_surgery'] > self.rng.random_sample() - # And store the treatment which will dramatically reduce risk of death - if treatment_success_pph and avail and sf_check: - self.pph_treatment.set(person_id, 'surgery') + # And store the treatment which will dramatically reduce risk of death + 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 + + # Log equipment + # Todo: link to surgical equipment package when that exsists + hsi_event.EQUIPMENT.update( + {'Hysterectomy set'}) - # If the treatment is unsuccessful then women will require a hysterectomy to stop the bleeding - 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 + self.pph_treatment.set(person_id, 'hysterectomy') + df.at[person_id, 'la_has_had_hysterectomy'] = True # Next we apply the effect of surgical treatment for women with retained placenta elif (mni[person_id]['retained_placenta'] and not self.pph_treatment.has_all(person_id, @@ -2136,6 +2148,11 @@ def surgical_management_of_pph(self, hsi_event): and sf_check and avail): self.pph_treatment.set(person_id, 'surgery') + # Log equipment + # Todo: link to surgical equipment package when that exsists + hsi_event.EQUIPMENT.update( + {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) + # log intervention delivery if self.pph_treatment.has_all(person_id, 'surgery') or df.at[person_id, 'la_has_had_hysterectomy']: pregnancy_helper_functions.log_met_need(self, 'pph_surg', hsi_event) @@ -2162,6 +2179,8 @@ def blood_transfusion(self, hsi_event): hsi_event=hsi_event) if avail and sf_check: + hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) + mni[person_id]['received_blood_transfusion'] = True pregnancy_helper_functions.log_met_need(self, 'blood_tran', hsi_event) @@ -2185,6 +2204,9 @@ def assessment_and_treatment_of_anaemia(self, hsi_event): mother = df.loc[person_id] mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info + # Log equipment + hsi_event.EQUIPMENT.update({'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) @@ -2853,6 +2875,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'NormalDelivery': 1}) self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 2}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -3033,6 +3056,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'PostnatalCare_Maternal' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = self._get_facility_level_for_pnc(person_id) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -3160,6 +3184,7 @@ def __init__(self, module, person_id, timing): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.timing = timing + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -3190,6 +3215,12 @@ def apply(self, person_id, squeeze_factor): hsi_event=self) if avail and sf_check or (mni[person_id]['cs_indication'] == 'other'): + + # If intervention is delivered - log equipment + # Todo: link to surgical equipment package when that exsists + self.EQUIPMENT.update( + {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) + person = df.loc[person_id] logger.info(key='caesarean_delivery', data=person.to_dict()) logger.info(key='cs_indications', data={'id': person_id, @@ -3223,6 +3254,8 @@ 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.EQUIPMENT.update( + {'Hysterectomy set'}) df.at[person_id, 'la_has_had_hysterectomy'] = True # ============================= SURGICAL MANAGEMENT OF POSTPARTUM HAEMORRHAGE================================== diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 1905ac1d9f..604c613c49 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -1008,6 +1008,9 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): df.at[person_id, 'nb_supp_care_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_supportive_care', hsi_event) + # Log equipment + hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) + # The same pattern is then followed for health centre care else: avail = pregnancy_helper_functions.return_cons_avail( @@ -1018,6 +1021,9 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): df.at[person_id, 'nb_inj_abx_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_abx', hsi_event) + # Log equipment + hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump', 'Oxygen cylinder, with regulator'}) + def link_twins(self, child_one, child_two, mother_id): """ This function links twin pairs via sibling IDs and is called by the BirthEvent in the Labour module @@ -1382,6 +1388,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'PostnatalCare_Neonatal' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = self._get_facility_level_for_pnc(person_id) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): nci = self.module.newborn_care_info From 1ed9b1f2c4dee76c257dcec29922e6f4160bdd7a Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 3 Nov 2023 14:06:45 +0000 Subject: [PATCH 208/443] ALRI equipment added (pulse ox, oxygen) --- src/tlo/methods/alri.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index e4b0b46d17..4915ae8364 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2290,6 +2290,8 @@ def __init__(self, module: Module, person_id: int, facility_level: str = "0", in self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels + self.EQUIPMENT = set() + self.is_followup_following_treatment_failure = is_followup_following_treatment_failure if not inpatient: @@ -2595,6 +2597,8 @@ def _get_disease_classification_for_treatment_decision(self, 'chest_indrawing_pneumonia', (symptoms-based assessment) 'cough_or_cold' (symptoms-based assessment) }.""" + # TODO: Currently this is logged as equipment even if pulse ox consumable isnt available + self.EQUIPMENT.update({'Pulse oximeter'}) child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) @@ -2650,6 +2654,16 @@ def _try_treatment(antibiotic_indicated: Tuple[str], oxygen_indicated: bool) -> oxygen_available = self._get_cons('Oxygen_Therapy') oxygen_provided = (oxygen_available and oxygen_indicated) + # todo: should equipment only be logged if consumables are available? + # If individual requires oxygen, log equipment + if oxygen_indicated: + self.EQUIPMENT.update({'Oxygen cylinder, with regulator', 'Nasal Prongs'}) + + # If individual requires intravenous antibiotics, log equipment + if antibiotic_indicated in ('1st_line_IV_antibiotics', + 'Benzylpenicillin_gentamicin_therapy_for_severe_pneumonia'): + self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + all_things_needed_available = antibiotic_available and ( (oxygen_available and oxygen_indicated) or (not oxygen_indicated) ) @@ -2731,12 +2745,15 @@ def _provide_bronchodilator_if_wheeze(self, facility_level, symptoms): if facility_level == '1a': _ = self._get_cons('Inhaled_Brochodilator') else: + # todo: determine if steroids here are IV (no consumables defined) _ = self._get_cons('Brochodilator_and_Steroids') def do_on_follow_up_following_treatment_failure(self): """Things to do for a patient who is having this HSI following a failure of an earlier treatment. A further drug will be used but this will have no effect on the chance of the person dying.""" + self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + if self._has_staph_aureus(): _ = self._get_cons('2nd_line_Antibiotic_therapy_for_severe_staph_pneumonia') else: From 3a8fd280b930df329f9ab037ebf8ee682221ac26 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 3 Nov 2023 15:30:34 +0000 Subject: [PATCH 209/443] cardio metabolic disorders equipment added --- src/tlo/methods/cardio_metabolic_disorders.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index 4ef5c41c8e..ed53a942f6 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1391,6 +1391,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "CardioMetabolicDisorders_Prevention_CommunityTestingForHypertension" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1402,6 +1403,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.EQUIPMENT.update({'Blood pressure machine'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='assess_hypertension', hsi_event=self @@ -1442,6 +1444,7 @@ def __init__(self, module, person_id, conditions_to_investigate: List, has_any_c self.ACCEPTED_FACILITY_LEVEL = '1b' self.conditions_to_investigate = conditions_to_investigate self.has_any_cmd_symptom = has_any_cmd_symptom + self.EQUIPMENT = set() def do_for_each_condition(self, _c) -> bool: """What to do for each condition that will be investigated. Returns `bool` signalling whether a follow-up HSI @@ -1454,7 +1457,6 @@ def do_for_each_condition(self, _c) -> bool: if df.at[person_id, f'nc_{_c}_ever_diagnosed']: return - # 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}', hsi_event=self @@ -1486,6 +1488,9 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Do test and trigger treatment (if necessary) for each condition: + if ('diabetes', 'chronic_kidney_disease', 'chronic_ischemic_hd') in self.conditions_to_investigate: + self.EQUIPMENT.update({'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 @@ -1509,6 +1514,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.EQUIPMENT.update({'Blood pressure machine'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='assess_hypertension', hsi_event=self @@ -1545,6 +1551,7 @@ def __init__(self, module, person_id, condition): self.TREATMENT_ID = 'CardioMetabolicDisorders_Prevention_WeightLoss' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() self.condition = condition @@ -1556,6 +1563,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.EQUIPMENT.update({'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), @@ -1692,6 +1701,9 @@ 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.EQUIPMENT.update({'Computed Tomography (CT machine)', 'CT scanner accessories'}) + dx_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run=f'assess_{_ev}', hsi_event=self @@ -1748,6 +1760,11 @@ def apply(self, person_id, squeeze_factor): data=('This is HSI_CardioMetabolicDisorders_SeeksEmergencyCareAndGetsTreatment: ' f'The squeeze-factor is {squeeze_factor}.'), ) + self.EQUIPMENT.update({'Analyser, Combined Chemistry and Electrolytes', + 'Analyser, Haematology', + 'Patient monitor', 'Drip stand', + 'Infusion pump', 'Blood pressure machine', + 'Pulse oximeter', 'Trolley, emergency'}) for _ev in self.events_to_investigate: self.do_for_each_event_to_be_investigated(_ev) From 38f07b3a6da12014e63c2855939417d8a8862c3b Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 3 Nov 2023 16:02:25 +0000 Subject: [PATCH 210/443] cardio metabolic disorders equipment added --- src/tlo/methods/cardio_metabolic_disorders.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index ed53a942f6..d3dabdacd9 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1457,6 +1457,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.EQUIPMENT.update({'Stethoscope'}) + dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run=f'assess_{_c}', hsi_event=self @@ -1764,7 +1767,7 @@ def apply(self, person_id, squeeze_factor): 'Analyser, Haematology', 'Patient monitor', 'Drip stand', 'Infusion pump', 'Blood pressure machine', - 'Pulse oximeter', 'Trolley, emergency'}) + 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope'}) for _ev in self.events_to_investigate: self.do_for_each_event_to_be_investigated(_ev) From 72b6a2aa890395581c5124525e92ac0141284419 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 3 Nov 2023 16:03:29 +0000 Subject: [PATCH 211/443] cardio metabolic disorders equipment added --- src/tlo/methods/cardio_metabolic_disorders.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index d3dabdacd9..ad4f7071e8 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1767,7 +1767,8 @@ def apply(self, person_id, squeeze_factor): 'Analyser, Haematology', 'Patient monitor', 'Drip stand', 'Infusion pump', 'Blood pressure machine', - 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope'}) + 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope', + 'Oxygen cylinder, with regulator'}) for _ev in self.events_to_investigate: self.do_for_each_event_to_be_investigated(_ev) From 29f9fc913e1d01350a47ac1a1b286dc188c5002e Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 3 Nov 2023 16:21:22 +0000 Subject: [PATCH 212/443] diarrhoea equipment added --- src/tlo/methods/diarrhoea.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tlo/methods/diarrhoea.py b/src/tlo/methods/diarrhoea.py index 242ecb2cc1..6b019a8e4c 100644 --- a/src/tlo/methods/diarrhoea.py +++ b/src/tlo/methods/diarrhoea.py @@ -736,6 +736,7 @@ def do_treatment(self, person_id, hsi_event): # STEP ONE: Aim to alleviate dehydration: prob_remove_dehydration = 0.0 if is_in_patient: + if hsi_event.get_consumables(item_codes=self.consumables_used_in_hsi['Treatment_Severe_Dehydration']): # In-patient receiving IV fluids (WHO Plan C) prob_remove_dehydration = \ @@ -1547,6 +1548,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 2}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """Run `do_treatment` for this person from an in-potient setting.""" @@ -1555,6 +1557,8 @@ def apply(self, person_id, squeeze_factor): if not df.at[person_id, 'is_alive']: return + self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + self.module.do_treatment(person_id=person_id, hsi_event=self) From 30d760eebd58165fe7c91e71f5364d647c5b5df9 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 10 Nov 2023 08:40:27 +0000 Subject: [PATCH 213/443] initial RTI equipment added. updated confusing naming of consumables as equipment in MNH modules --- .../methods/care_of_women_during_pregnancy.py | 26 +++++++++---------- src/tlo/methods/labour.py | 23 ++++++++-------- src/tlo/methods/newborn_outcomes.py | 12 ++++----- src/tlo/methods/rti.py | 9 ++++++- 4 files changed, 37 insertions(+), 33 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index c7f5009c2b..39684e432a 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -215,14 +215,12 @@ def get_and_store_pregnancy_item_codes(self): """ get_list_of_items = pregnancy_helper_functions.get_list_of_items - # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing - self.item_codes_preg_consumables['blood_test_equipment'] = \ + # ---------------------------------- BLOOD TEST CONSUMABLES -------------------------------------------------- + self.item_codes_preg_consumables['blood_test_cons'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) - # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- - self.item_codes_preg_consumables['iv_drug_equipment'] = \ + # ---------------------------------- IV DRUG ADMIN CONSUMABLES ----------------------------------------------- + self.item_codes_preg_consumables['iv_drug_cons'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', 'Disposables gloves, powder free, 100 pieces per box']) @@ -922,7 +920,7 @@ def point_of_care_hb_testing(self, hsi_event): if self.rng.random_sample() < params['prob_intervention_delivered_poct']: logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'hb_screen'}) - hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_equipment']) + hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_cons']) hsi_event.EQUIPMENT.update({'Haemoglobinometer'}) # We run the test through the dx_manager and if a woman has anaemia and its detected she will be admitted @@ -971,7 +969,7 @@ def hep_b_testing(self, hsi_event): # This intervention is a place holder prior to the Hepatitis B module being coded # Define the consumables avail = hsi_event.get_consumables(item_codes=cons['hep_b_test'], - optional_item_codes=cons['blood_test_equipment']) + optional_item_codes=cons['blood_test_cons']) # We log all the consumables required above but we only condition the event test happening on the # availability of the test itself @@ -998,7 +996,7 @@ def syphilis_screening_and_treatment(self, hsi_event): avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_preg_consumables, core='syphilis_test', - optional='blood_test_equipment') + optional='blood_test_cons') test = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run='blood_test_syphilis', hsi_event=hsi_event) @@ -1008,7 +1006,7 @@ def syphilis_screening_and_treatment(self, hsi_event): avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_preg_consumables, core='syphilis_treatment', - optional='blood_test_equipment') + optional='blood_test_cons') if avail: # We assume that treatment is 100% effective at curing infection @@ -1073,7 +1071,7 @@ def gdm_screening(self, hsi_event): if self.rng.random_sample() < params['prob_intervention_delivered_gdm_test']: avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, self.item_codes_preg_consumables, core='gdm_test', optional='iv_drug_equipment') + self, hsi_event, self.item_codes_preg_consumables, core='gdm_test', optional='iv_drug_cons') # If the test accurately detects a woman has gestational diabetes the consumables are recorded and # she is referred for treatment @@ -1249,7 +1247,7 @@ def antenatal_blood_transfusion(self, individual_id, hsi_event): # Check for consumables avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, self.item_codes_preg_consumables, core='blood_transfusion', optional='iv_drug_equipment') + self, hsi_event, self.item_codes_preg_consumables, core='blood_transfusion', optional='iv_drug_cons') sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], sf='blood_tran', @@ -1357,7 +1355,7 @@ def antibiotics_for_prom(self, individual_id, hsi_event): # check consumables and whether HCW are available to deliver the intervention avail = hsi_event.get_consumables(item_codes=cons['abx_for_prom'], - optional_item_codes=cons['iv_drug_equipment']) + optional_item_codes=cons['iv_drug_cons']) sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], sf='iv_abx', @@ -2649,7 +2647,7 @@ def apply(self, person_id, squeeze_factor): cons_for_haemorrhage = pregnancy_helper_functions.return_cons_avail( self.module, self, self.module.item_codes_preg_consumables, core='blood_transfusion', - optional='iv_drug_equipment') + optional='iv_drug_cons') cons_for_shock = pregnancy_helper_functions.return_cons_avail( self.module, self, self.module.item_codes_preg_consumables, core='post_abortion_care_shock', diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 0c55d10f9b..b5560a10c7 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -691,16 +691,15 @@ def get_and_store_labour_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items - # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing - self.item_codes_lab_consumables['iv_drug_equipment'] = \ + # ---------------------------------- IV DRUG ADMIN CONSUMABLES ----------------------------------------------- + + self.item_codes_lab_consumables['iv_drug_cons'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', 'Disposables gloves, powder free, 100 pieces per box']) - # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- - self.item_codes_lab_consumables['blood_test_equipment'] = \ + # ---------------------------------- BLOOD TEST CONSUMABLES --------------------------------------------------- + self.item_codes_lab_consumables['blood_test_cons'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) # -------------------------------------------- DELIVERY ------------------------------------------------------ @@ -1660,7 +1659,7 @@ def prophylactic_labour_interventions(self, hsi_event): # If she has not already receive antibiotics, we check for consumables avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, self.item_codes_lab_consumables, core='abx_for_prom', optional='iv_drug_equipment') + self, hsi_event, self.item_codes_lab_consumables, core='abx_for_prom', optional='iv_drug_cons') # Then query if these consumables are available during this HSI And provide if available. # Antibiotics for from reduce risk of newborn sepsis within the first @@ -1675,7 +1674,7 @@ def prophylactic_labour_interventions(self, hsi_event): avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_lab_consumables, core='antenatal_steroids', - optional='iv_drug_equipment') + optional='iv_drug_cons') # If available they are given. Antenatal steroids reduce a preterm newborns chance of developing # respiratory distress syndrome and of death associated with prematurity @@ -1770,7 +1769,7 @@ def assessment_and_treatment_of_hypertension(self, hsi_event, labour_stage): # Then query if these consumables are available during this HSI avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_lab_consumables, core='iv_antihypertensives', - optional='iv_drug_equipment') + optional='iv_drug_cons') # If they are available then the woman is started on treatment. Intravenous antihypertensive reduce a # womans risk of progression from mild to severe gestational hypertension ANd reduce risk of death for @@ -1994,7 +1993,7 @@ def active_management_of_the_third_stage_of_labour(self, hsi_event): # Define and check available consumables avail = pregnancy_helper_functions.return_cons_avail( - self, hsi_event, self.item_codes_lab_consumables, core='amtsl', optional='iv_drug_equipment') + self, hsi_event, self.item_codes_lab_consumables, core='amtsl', optional='iv_drug_cons') # run HCW check sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='uterotonic', @@ -2172,7 +2171,7 @@ def blood_transfusion(self, hsi_event): # Check consumables avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_lab_consumables, core='blood_transfusion', - optional='iv_drug_equipment') + optional='iv_drug_cons') # check HCW sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='blood_tran', @@ -2211,7 +2210,7 @@ def assessment_and_treatment_of_anaemia(self, hsi_event): test_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run='full_blood_count_hb_pn', hsi_event=hsi_event) - hsi_event.get_consumables(item_codes=self.item_codes_lab_consumables['blood_test_equipment']) + hsi_event.get_consumables(item_codes=self.item_codes_lab_consumables['blood_test_cons']) # Check consumables if test_result: diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 604c613c49..44f5af7c28 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -409,16 +409,16 @@ def get_and_store_newborn_item_codes(self): """ get_list_of_items = pregnancy_helper_functions.get_list_of_items - # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # ---------------------------------- IV DRUG ADMIN CONSUMABLE ------------------------------------------------- # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is # confusing - self.item_codes_nb_consumables['iv_drug_equipment'] = \ + self.item_codes_nb_consumables['iv_drug_cons'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', 'Disposables gloves, powder free, 100 pieces per box']) - # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- - self.item_codes_nb_consumables['blood_test_equipment'] = \ + # ---------------------------------- BLOOD TEST CONSUMABLES --------------------------------------------------- + self.item_codes_nb_consumables['blood_test_cons'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) # -------------------------------------------- VITAMIN K ------------------------------------------ @@ -861,7 +861,7 @@ def essential_newborn_care(self, hsi_event): # We define the consumables avail_eyecare = hsi_event.get_consumables(item_codes=cons['eye_care']) avail_vit_k = hsi_event.get_consumables(item_codes=cons['vitamin_k'], - optional_item_codes=cons['iv_drug_equipment']) + optional_item_codes=cons['iv_drug_cons']) # If they are available the intervention is delivered, there is limited evidence of the effect of these # interventions so currently we are just mapping the consumables @@ -1015,7 +1015,7 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): else: avail = pregnancy_helper_functions.return_cons_avail( self, hsi_event, self.item_codes_nb_consumables, core='sepsis_abx', - optional='iv_drug_equipment') + optional='iv_drug_cons') if avail and sf_check: df.at[person_id, 'nb_inj_abx_neonatal_sepsis'] = True diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index f92d751e14..9ec5adb9bc 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3428,6 +3428,7 @@ def apply(self, person_id, squeeze_factor): # mortality percentage = 51.2 overall, 50% for TBI admission and 49% for hemorrhage # determine the number of ICU days used to treat patient + # TODO: Add general ICU equipment here? What form of organ support is offered? if df.loc[person_id, 'rt_ISS_score'] > self.hdu_cut_off_iss_score: mean_icu_days = p['mean_icu_days'] sd_icu_days = p['sd_icu_days'] @@ -3722,7 +3723,7 @@ 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 + of shock TODO: is this still the case - should this be scheduled/ignored? """ def __init__(self, module, person_id): @@ -3732,6 +3733,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_ShockTreatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -3743,6 +3745,10 @@ def apply(self, person_id, squeeze_factor): if not df.at[person_id, 'is_alive']: return self.make_appt_footprint({}) get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name + + self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Oxygen cylinder, with regulator', 'Nasal Prongs', + 'Patient monitor', 'Blood pressure machine', 'Pulse oximeter'}) + # TODO: find a more complete list of required consumables for adults if is_child: self.module.item_codes_for_consumables_required['shock_treatment_child'] = { @@ -3824,6 +3830,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_FractureCast' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): # Get the population and health system From 0f16f590b963cc55da4ac170d2350668450685f8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 13 Nov 2023 21:54:17 +0000 Subject: [PATCH 214/443] diarr: empty equip declaration --- src/tlo/methods/diarrhoea.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tlo/methods/diarrhoea.py b/src/tlo/methods/diarrhoea.py index 6b019a8e4c..7b0109b324 100644 --- a/src/tlo/methods/diarrhoea.py +++ b/src/tlo/methods/diarrhoea.py @@ -1524,6 +1524,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Diarrhoea_Treatment_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """Run `do_treatment` for this person from an out-potient setting.""" From 5354899eca963249905868a59b120a0f381174f9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 13 Nov 2023 22:21:39 +0000 Subject: [PATCH 215/443] ac: finish update of confusing naming of consumables as equipment --- src/tlo/methods/care_of_women_during_pregnancy.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 39684e432a..2944ee41fa 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1215,7 +1215,7 @@ def full_blood_count_testing(self, hsi_event): # Run dx_test for anaemia... # If a woman is not truly anaemic but the FBC returns a result of anaemia, due to tests specificity, we # assume the reported anaemia is mild - hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_equipment']) + hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_cons']) hsi_event.EQUIPMENT.update({'Analyser, Haematology'}) test_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( @@ -1298,7 +1298,7 @@ def initiate_treatment_for_severe_hypertension(self, individual_id, hsi_event): # Define the consumables and check their availability avail = hsi_event.get_consumables(item_codes=cons['iv_antihypertensives'], - optional_item_codes=cons['iv_drug_equipment']) + optional_item_codes=cons['iv_drug_cons']) # If they are available then the woman is started on treatment if avail: From 7adde5f489c62bfeaf0c86db98c1b516afb94a60 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 13 Nov 2023 22:22:04 +0000 Subject: [PATCH 216/443] nb: rm finished TODO --- src/tlo/methods/newborn_outcomes.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 44f5af7c28..9c0b58084e 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -410,8 +410,6 @@ def get_and_store_newborn_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN CONSUMABLE ------------------------------------------------- - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing self.item_codes_nb_consumables['iv_drug_cons'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', From b7ad9002073ed4e660710963643266474f9c8471 Mon Sep 17 00:00:00 2001 From: tdm32 Date: Mon, 6 Nov 2023 12:47:52 +0000 Subject: [PATCH 217/443] add equipment declaration for EPI module HsiBaseVaccine class --- src/tlo/methods/epi.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tlo/methods/epi.py b/src/tlo/methods/epi.py index 912edbf459..2d5f442752 100644 --- a/src/tlo/methods/epi.py +++ b/src/tlo/methods/epi.py @@ -395,6 +395,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi="1a", suppress_ self.TREATMENT_ID = self.treatment_id() self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi + self.EQUIPMENT = set() # no equipment required def treatment_id(self): """subclasses should implement this method to return the TREATMENT_ID""" From 7295d1a46fbf9ff384d4df0e82841164a28c4b6d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 13 Nov 2023 23:25:51 +0000 Subject: [PATCH 218/443] stunting: empty equipment declaration --- src/tlo/methods/stunting.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tlo/methods/stunting.py b/src/tlo/methods/stunting.py index b4122b423a..004deb0d11 100644 --- a/src/tlo/methods/stunting.py +++ b/src/tlo/methods/stunting.py @@ -533,6 +533,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Undernutrition_Feeding' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'U5Malnutr': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): From ae05be9fe8a726e19134728d81058ab4bfbcf668 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 14 Nov 2023 18:49:11 +0000 Subject: [PATCH 219/443] brc & co: add TODOs to replace dummy examples by real equip items diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 402266516..57e842119 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -646,8 +646,9 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - # TODO: but the appt footprints suggests mammography to be provided - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + + # TODO: Eva's dummy equipment example (not sure if it actually needs to be added and if it is in the RF) + # {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} # ap_oct23 - Eva, I cannot locate a biopsy needle in the equipment - perhaps it is in consumables # the other equipment need is for histology in the lab - there is a whole long list of items needed @@ -771,8 +772,9 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # TODO: Eva's dummy equipment example - it needs to be replaced by real items from the RF # Update equipment - self.EQUIPMENT.update({'Anything used for mastectomy'}) + # self.EQUIPMENT.update({'Anything used for mastectomy'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 6f9e4254b..8c9821b0c 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1284,8 +1284,10 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) # Update equipment if _new_contraceptive == 'female_sterilization': + # TODO: Eva's dummy example - needs to be replaced by real item(s) self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': + # TODO: Eva's dummy example - needs to be replaced by real item(s) self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: --- src/tlo/methods/breast_cancer.py | 8 +++++--- src/tlo/methods/contraception.py | 2 ++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 402266516c..57e8421194 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,8 +646,9 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - # TODO: but the appt footprints suggests mammography to be provided - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + + # TODO: Eva's dummy equipment example (not sure if it actually needs to be added and if it is in the RF) + # {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} # ap_oct23 - Eva, I cannot locate a biopsy needle in the equipment - perhaps it is in consumables # the other equipment need is for histology in the lab - there is a whole long list of items needed @@ -771,8 +772,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # TODO: Eva's dummy equipment example - it needs to be replaced by real items from the RF # Update equipment - self.EQUIPMENT.update({'Anything used for mastectomy'}) + # self.EQUIPMENT.update({'Anything used for mastectomy'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 6f9e4254b5..8c9821b0c5 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1284,8 +1284,10 @@ def apply(self, person_id, squeeze_factor): # Update equipment if _new_contraceptive == 'female_sterilization': + # TODO: Eva's dummy example - needs to be replaced by real item(s) self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': + # TODO: Eva's dummy example - needs to be replaced by real item(s) self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: From f9cca9543835e789e1aaa44611c0192fdb87b0cd Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 00:06:26 +0000 Subject: [PATCH 220/443] fix mistakes from the rebase diff --git src/tlo/methods/care_of_women_during_pregnancy.py src/tlo/methods/care_of_women_during_pregnancy.py index 2944ee41f..96a4b6975 100644 --- src/tlo/methods/care_of_women_during_pregnancy.py +++ src/tlo/methods/care_of_women_during_pregnancy.py @@ -572,7 +572,7 @@ class CareOfWomenDuringPregnancy(Module): def antenatal_care_scheduler(self, individual_id, visit_to_be_scheduled, recommended_gestation_next_anc): """ - This function is responsible for scheduling a womans next ANC contact in the schedule if she chooses to seek + This function is responsible for scheduling a woman's next ANC contact in the schedule if she chooses to seek care again. It is called by each of the ANC HSIs. :param individual_id: individual_id :param visit_to_be_scheduled: Number if next visit in the schedule (2-8) @@ -1572,8 +1572,8 @@ class HSI_CareOfWomenDuringPregnancy_SecondAntenatalCareContact(HSI_Event, Indiv self.module.antenatal_care_scheduler(person_id, visit_to_be_scheduled=3, recommended_gestation_next_anc=gest_age_next_contact) - # Then we administer interventions that are due to be delivered at this womans gestational age, which may be - # in addition to intervention delivered in ANC2 + # Then we administer interventions that are due to be delivered at this woman's gestational age, which may + # be in addition to intervention delivered in ANC2 if mother.ps_gestational_age_in_weeks < 26: self.module.albendazole_administration(hsi_event=self) self.module.iptp_administration(hsi_event=self) @@ -1621,7 +1621,7 @@ class HSI_CareOfWomenDuringPregnancy_ThirdAntenatalCareContact(HSI_Event, Indivi antenatal care contact (ANC3). It is scheduled by the HSI_CareOfWomenDuringPregnancy_SecondAntenatalCareContact for women who choose to seek additional ANC after their previous visit. It is recommended that this visit occur at 26 weeks gestation. This event delivers the interventions to women which are part of ANC3. Additionally interventions - that should be delivered according to a womans gestational age and position in her ANC schedule are delivered. + that should be delivered according to a woman's gestational age and position in her ANC schedule are delivered. Finally scheduling the next ANC contact in the occurs during this HSI along with admission to antenatal inpatient ward in the case of complications""" --- src/tlo/methods/care_of_women_during_pregnancy.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 2944ee41fa..96a4b69752 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -572,7 +572,7 @@ def determine_gestational_age_for_next_contact(self, person_id): def antenatal_care_scheduler(self, individual_id, visit_to_be_scheduled, recommended_gestation_next_anc): """ - This function is responsible for scheduling a womans next ANC contact in the schedule if she chooses to seek + This function is responsible for scheduling a woman's next ANC contact in the schedule if she chooses to seek care again. It is called by each of the ANC HSIs. :param individual_id: individual_id :param visit_to_be_scheduled: Number if next visit in the schedule (2-8) @@ -1572,8 +1572,8 @@ def apply(self, person_id, squeeze_factor): self.module.antenatal_care_scheduler(person_id, visit_to_be_scheduled=3, recommended_gestation_next_anc=gest_age_next_contact) - # Then we administer interventions that are due to be delivered at this womans gestational age, which may be - # in addition to intervention delivered in ANC2 + # Then we administer interventions that are due to be delivered at this woman's gestational age, which may + # be in addition to intervention delivered in ANC2 if mother.ps_gestational_age_in_weeks < 26: self.module.albendazole_administration(hsi_event=self) self.module.iptp_administration(hsi_event=self) @@ -1621,7 +1621,7 @@ class HSI_CareOfWomenDuringPregnancy_ThirdAntenatalCareContact(HSI_Event, Indivi antenatal care contact (ANC3). It is scheduled by the HSI_CareOfWomenDuringPregnancy_SecondAntenatalCareContact for women who choose to seek additional ANC after their previous visit. It is recommended that this visit occur at 26 weeks gestation. This event delivers the interventions to women which are part of ANC3. Additionally interventions - that should be delivered according to a womans gestational age and position in her ANC schedule are delivered. + that should be delivered according to a woman's gestational age and position in her ANC schedule are delivered. Finally scheduling the next ANC contact in the occurs during this HSI along with admission to antenatal inpatient ward in the case of complications""" From 312aaa3ad9093164aa17ff09ba70b096d4a38281 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 6 Dec 2023 15:18:16 +0000 Subject: [PATCH 221/443] COPD equipment added. missing equipment declarations added --- src/tlo/methods/copd.py | 4 ++++ src/tlo/methods/labour.py | 1 + src/tlo/methods/newborn_outcomes.py | 3 +-- src/tlo/methods/postnatal_supervisor.py | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/copd.py b/src/tlo/methods/copd.py index 2fd27e13a2..5c27791985 100644 --- a/src/tlo/methods/copd.py +++ b/src/tlo/methods/copd.py @@ -521,12 +521,16 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = self.all_facility_levels[self.facility_levels_index] self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 2}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """What to do when someone presents for care with an exacerbation. * Provide treatment: whatever is available at this facility at this time (no referral). """ df = self.sim.population.props + + self.EQUIPMENT.update({'Oxygen cylinder, with regulator', 'Nasal Prongs', 'Drip stand', 'Infusion pump'}) + if not self.get_consumables(self.module.item_codes['oxygen']): # refer to the next higher facility if the current facility has no oxygen self.facility_levels_index += 1 diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index b5560a10c7..c45a484b11 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -3322,6 +3322,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 5}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): logger.debug(key='message', data='HSI_Labour_PostnatalWardInpatientCare now running to capture ' diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 44f5af7c28..b972f1aac3 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -410,8 +410,6 @@ def get_and_store_newborn_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN CONSUMABLE ------------------------------------------------- - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing self.item_codes_nb_consumables['iv_drug_cons'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', @@ -1480,6 +1478,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index caed0527e4..406a0208ed 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -1282,6 +1282,7 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self.ALERT_OTHER_DISEASES = [] self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props From f1d4ee0463a452c279e220624088b00be4eb8506 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Wed, 6 Dec 2023 15:51:18 +0000 Subject: [PATCH 222/443] updates to MNH modules, ALRI and CKD following review comments --- src/tlo/methods/alri.py | 4 ++-- src/tlo/methods/cardio_metabolic_disorders.py | 5 ++++- src/tlo/methods/labour.py | 12 ++++++------ src/tlo/methods/newborn_outcomes.py | 4 ++-- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index 4915ae8364..d06a38601a 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2655,11 +2655,11 @@ def _try_treatment(antibiotic_indicated: Tuple[str], oxygen_indicated: bool) -> oxygen_provided = (oxygen_available and oxygen_indicated) # todo: should equipment only be logged if consumables are available? - # If individual requires oxygen, log equipment + # If individual requires oxygen, update equipment if oxygen_indicated: self.EQUIPMENT.update({'Oxygen cylinder, with regulator', 'Nasal Prongs'}) - # If individual requires intravenous antibiotics, log equipment + # If individual requires intravenous antibiotics, update equipment if antibiotic_indicated in ('1st_line_IV_antibiotics', 'Benzylpenicillin_gentamicin_therapy_for_severe_pneumonia'): self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index ad4f7071e8..66ed6cbd0b 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1491,7 +1491,8 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Do test and trigger treatment (if necessary) for each condition: - if ('diabetes', 'chronic_kidney_disease', 'chronic_ischemic_hd') in self.conditions_to_investigate: + if any(cond in self.conditions_to_investigate for cond in + ('diabetes', 'chronic_kidney_disease', 'chronic_ischemic_hd')): self.EQUIPMENT.update({'Analyser, Haematology', 'Analyser, Combined Chemistry and Electrolytes'}) hsi_scheduled = [self.do_for_each_condition(_c) for _c in self.conditions_to_investigate] @@ -1625,6 +1626,7 @@ def __init__(self, module, person_id, condition): self.TREATMENT_ID = 'CardioMetabolicDisorders_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() self.condition = condition @@ -1696,6 +1698,7 @@ def __init__(self, module, person_id, events_to_investigate: List): self.TREATMENT_ID = 'CardioMetabolicDisorders_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '2' + self.EQUIPMENT = set() self.events_to_investigate = events_to_investigate def do_for_each_event_to_be_investigated(self, _ev): diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index c45a484b11..a216c7c996 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1865,7 +1865,7 @@ def refer_for_cs(): sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='avd', hsi_event=hsi_event) - # log equipment + # Update equipment hsi_event.EQUIPMENT.update({'Delivery Forceps', 'Vacuum extractor'}) if avail and sf_check: @@ -2119,7 +2119,7 @@ def surgical_management_of_pph(self, hsi_event): if avail and sf_check: - # Log equipment + # Update equipment # Todo: link to surgical equipment package when that exsists hsi_event.EQUIPMENT.update( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -2133,7 +2133,7 @@ def surgical_management_of_pph(self, hsi_event): else: # If the treatment is unsuccessful then women will require a hysterectomy to stop the bleeding - # Log equipment + # Update equipment # Todo: link to surgical equipment package when that exsists hsi_event.EQUIPMENT.update( {'Hysterectomy set'}) @@ -2147,7 +2147,7 @@ def surgical_management_of_pph(self, hsi_event): and sf_check and avail): self.pph_treatment.set(person_id, 'surgery') - # Log equipment + # Update equipment # Todo: link to surgical equipment package when that exsists hsi_event.EQUIPMENT.update( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -2203,7 +2203,7 @@ def assessment_and_treatment_of_anaemia(self, hsi_event): mother = df.loc[person_id] mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info - # Log equipment + # Update equipment hsi_event.EQUIPMENT.update({'Analyser, Haematology'}) # Use dx_test function to assess anaemia status @@ -3215,7 +3215,7 @@ def apply(self, person_id, squeeze_factor): if avail and sf_check or (mni[person_id]['cs_indication'] == 'other'): - # If intervention is delivered - log equipment + # If intervention is delivered - update equipment # Todo: link to surgical equipment package when that exsists self.EQUIPMENT.update( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index b972f1aac3..0d5fced148 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -1006,7 +1006,7 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): df.at[person_id, 'nb_supp_care_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_supportive_care', hsi_event) - # Log equipment + # Update equipment hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) # The same pattern is then followed for health centre care @@ -1019,7 +1019,7 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): df.at[person_id, 'nb_inj_abx_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_abx', hsi_event) - # Log equipment + # Update equipment hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump', 'Oxygen cylinder, with regulator'}) def link_twins(self, child_one, child_two, mother_id): From 4325c7876de0e7ab847dacdc02781844df05726a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 16:54:53 +0000 Subject: [PATCH 223/443] equip_catalogue: (1) detailed - equip set as string in one row, module_name before event_name, sorted by indexes; (2) added summary catal. (3) focused - only this cat. without empty equip rows and split by item per row --- .../equipment/equipment_catalogue.py | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 66fcff01ae..6ba03916c8 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -99,8 +99,9 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' # requested details only CSV name time_index = 'year' if catalog_by_time == 'annual' else 'date' - output_file_name = \ + output_focused_file_name = \ 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' + output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id.csv' # --- # %% Catalog equipment by all HSI event details @@ -113,6 +114,12 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): def details_col_to_str(details_col): return details_col.apply(lambda x: ', '.join(map(str, x))) + def lists_of_strings_to_strings_of_list(list_of_strings_col): + return list_of_strings_col.apply(lambda x: "['" + "', '".join(map(str, x)) + "']") + + def strings_of_list_to_lists_of_strings(strings_of_list_col): + return strings_of_list_col.apply(lambda x: x.strip('][').split(', ')) + for col in hsi_event_keys.columns: df_col = sim_equipment_df[col].dropna() decoded_keys = df_col.index.get_level_values(1).astype(str).map(hsi_event_keys.at[0, col]) @@ -136,34 +143,38 @@ def details_col_to_str(details_col): # %%% df_col = pd.concat([df_col, pd.DataFrame(decoded_keys.tolist(), index=df_col.index)], axis=1) - # Make values in 'appt_footprint', 'beddays_footprint' columns to be string + # Make values in 'appt_footprint', 'beddays_footprint', and 'equipment' columns to be string df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) - # Explode the 'equipment' column - exploded_df = df_col.explode('equipment') - # Remove the 'event_details_key' and replace the index with hsi event details as indexes - exploded_df = exploded_df.droplevel(level=1) - exploded_df = exploded_df.set_index( - ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', - 'equipment'], append=True - ) - # Sum values with the same multi-index (keep also empty indexes) - exploded_df = exploded_df.groupby(level=exploded_df.index.names, dropna=False).sum() - # Add the results for the run 'col' to final_df - final_df = pd.concat([final_df, exploded_df], axis=1) + df_col['equipment'] = lists_of_strings_to_strings_of_list(df_col['equipment']) + df_col = (df_col.droplevel(level=1) + .set_index(['module_name', 'event_name', 'treatment_id', 'facility_level', 'appt_footprint', + 'beddays_footprint', 'equipment'], append=True)) + final_df = pd.concat([final_df, df_col], axis=1) # Replace NaN with 0 final_df.fillna(0, inplace=True) + final_df.sort_index(inplace=True) # Save the detailed equipment catalogue final_df.to_csv(output_folder / output_detailed_file_name) print(f'{output_detailed_file_name} saved.') # --- + # %% Catalog equipment summary + equipment_summary = final_df.copy() + equipment_summary = equipment_summary.groupby(['module_name', 'event_name', 'treatment_id', 'equipment']).sum() + equipment_summary = \ + equipment_summary.reset_index().set_index(['module_name', 'event_name', 'treatment_id', 'equipment']) + # Save the summary equipment catalogue + equipment_summary.index.to_frame().to_csv(output_folder / output_summary_file_name, index=False) + print(f'{output_summary_file_name} saved.') + # --- + # %% Catalog equipment by requested details equipment_counts_by_time_and_requested_details = final_df.copy() - # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), - # keeping only non-empty 'equipment' indexes + # Sum counts for each equipment set with the same date, treatment id, and facility level + # (remaining indexes removed), keeping only non-empty 'equipment' indexes to_be_grouped_by = ['date'] + catalog_by_details + ['equipment'] equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( to_be_grouped_by, @@ -181,9 +192,24 @@ def details_col_to_str(details_col): to_be_grouped_by ).sum() + # Remove rows with no equipment used + equipment_counts_by_time_and_requested_details.drop("['']", level='equipment', axis=0, inplace=True) + # Split the equipment by an item per row + equipment_counts_by_time_and_requested_details['equipment'] = \ + equipment_counts_by_time_and_requested_details.index.get_level_values('equipment') + equipment_counts_by_time_and_requested_details.index = \ + equipment_counts_by_time_and_requested_details.index.droplevel('equipment') + equipment_counts_by_time_and_requested_details['equipment'] = strings_of_list_to_lists_of_strings( + equipment_counts_by_time_and_requested_details['equipment'] + ) + exploded_df = equipment_counts_by_time_and_requested_details.explode('equipment') + exploded_df = exploded_df.set_index(['equipment'], append=True) + # Sum values with the same multi-index + exploded_df = exploded_df.groupby(level=exploded_df.index.names).sum() + # Save the equipment counts CSV - equipment_counts_by_time_and_requested_details.to_csv(output_folder / output_file_name) - print(f'{output_file_name} saved.') + exploded_df.to_csv(output_folder / output_focused_file_name) + print(f'{output_focused_file_name} saved.') # --- return 0 From b42c15b2fe65e192b18535434fe07fbb719e93d3 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 18:30:19 +0000 Subject: [PATCH 224/443] co: equipment updated --- src/tlo/methods/contraception.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 8c9821b0c5..bdb4cecc83 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1283,12 +1283,13 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive # Update equipment + self.EQUIPMENT.update({'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine'}) if _new_contraceptive == 'female_sterilization': - # TODO: Eva's dummy example - needs to be replaced by real item(s) - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) + self.EQUIPMENT.update({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise', 'Examination couch' + }) elif _new_contraceptive == 'IUD': - # TODO: Eva's dummy example - needs to be replaced by real item(s) - self.EQUIPMENT.update({'Equipment used when performing IUD'}) + self.EQUIPMENT.update({'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps', 'Examination couch'}) else: _new_contraceptive = "not_using" From 6ff025c3b95859aa43486be644c39bc05c1fcd47 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 16:54:53 +0000 Subject: [PATCH 225/443] equip_catalogue: (1) detailed - equip set as string in one row, module_name before event_name, sorted by indexes; (2) added summary catal. (3) focused - only this cat. without empty equip rows and split by item per row --- .../equipment/equipment_catalogue.py | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 66fcff01ae..6ba03916c8 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -99,8 +99,9 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' # requested details only CSV name time_index = 'year' if catalog_by_time == 'annual' else 'date' - output_file_name = \ + output_focused_file_name = \ 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' + output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id.csv' # --- # %% Catalog equipment by all HSI event details @@ -113,6 +114,12 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): def details_col_to_str(details_col): return details_col.apply(lambda x: ', '.join(map(str, x))) + def lists_of_strings_to_strings_of_list(list_of_strings_col): + return list_of_strings_col.apply(lambda x: "['" + "', '".join(map(str, x)) + "']") + + def strings_of_list_to_lists_of_strings(strings_of_list_col): + return strings_of_list_col.apply(lambda x: x.strip('][').split(', ')) + for col in hsi_event_keys.columns: df_col = sim_equipment_df[col].dropna() decoded_keys = df_col.index.get_level_values(1).astype(str).map(hsi_event_keys.at[0, col]) @@ -136,34 +143,38 @@ def details_col_to_str(details_col): # %%% df_col = pd.concat([df_col, pd.DataFrame(decoded_keys.tolist(), index=df_col.index)], axis=1) - # Make values in 'appt_footprint', 'beddays_footprint' columns to be string + # Make values in 'appt_footprint', 'beddays_footprint', and 'equipment' columns to be string df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) - # Explode the 'equipment' column - exploded_df = df_col.explode('equipment') - # Remove the 'event_details_key' and replace the index with hsi event details as indexes - exploded_df = exploded_df.droplevel(level=1) - exploded_df = exploded_df.set_index( - ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', - 'equipment'], append=True - ) - # Sum values with the same multi-index (keep also empty indexes) - exploded_df = exploded_df.groupby(level=exploded_df.index.names, dropna=False).sum() - # Add the results for the run 'col' to final_df - final_df = pd.concat([final_df, exploded_df], axis=1) + df_col['equipment'] = lists_of_strings_to_strings_of_list(df_col['equipment']) + df_col = (df_col.droplevel(level=1) + .set_index(['module_name', 'event_name', 'treatment_id', 'facility_level', 'appt_footprint', + 'beddays_footprint', 'equipment'], append=True)) + final_df = pd.concat([final_df, df_col], axis=1) # Replace NaN with 0 final_df.fillna(0, inplace=True) + final_df.sort_index(inplace=True) # Save the detailed equipment catalogue final_df.to_csv(output_folder / output_detailed_file_name) print(f'{output_detailed_file_name} saved.') # --- + # %% Catalog equipment summary + equipment_summary = final_df.copy() + equipment_summary = equipment_summary.groupby(['module_name', 'event_name', 'treatment_id', 'equipment']).sum() + equipment_summary = \ + equipment_summary.reset_index().set_index(['module_name', 'event_name', 'treatment_id', 'equipment']) + # Save the summary equipment catalogue + equipment_summary.index.to_frame().to_csv(output_folder / output_summary_file_name, index=False) + print(f'{output_summary_file_name} saved.') + # --- + # %% Catalog equipment by requested details equipment_counts_by_time_and_requested_details = final_df.copy() - # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), - # keeping only non-empty 'equipment' indexes + # Sum counts for each equipment set with the same date, treatment id, and facility level + # (remaining indexes removed), keeping only non-empty 'equipment' indexes to_be_grouped_by = ['date'] + catalog_by_details + ['equipment'] equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( to_be_grouped_by, @@ -181,9 +192,24 @@ def details_col_to_str(details_col): to_be_grouped_by ).sum() + # Remove rows with no equipment used + equipment_counts_by_time_and_requested_details.drop("['']", level='equipment', axis=0, inplace=True) + # Split the equipment by an item per row + equipment_counts_by_time_and_requested_details['equipment'] = \ + equipment_counts_by_time_and_requested_details.index.get_level_values('equipment') + equipment_counts_by_time_and_requested_details.index = \ + equipment_counts_by_time_and_requested_details.index.droplevel('equipment') + equipment_counts_by_time_and_requested_details['equipment'] = strings_of_list_to_lists_of_strings( + equipment_counts_by_time_and_requested_details['equipment'] + ) + exploded_df = equipment_counts_by_time_and_requested_details.explode('equipment') + exploded_df = exploded_df.set_index(['equipment'], append=True) + # Sum values with the same multi-index + exploded_df = exploded_df.groupby(level=exploded_df.index.names).sum() + # Save the equipment counts CSV - equipment_counts_by_time_and_requested_details.to_csv(output_folder / output_file_name) - print(f'{output_file_name} saved.') + exploded_df.to_csv(output_folder / output_focused_file_name) + print(f'{output_focused_file_name} saved.') # --- return 0 From bf83c61f2df0ca255650fae37d2692055ce94f79 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 21:30:58 +0000 Subject: [PATCH 226/443] equip_catalogue: suffix (as input) added for output file names --- .../healthsystem/equipment/equipment_catalogue.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 6ba03916c8..4506892e8b 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -16,6 +16,8 @@ # Declare which time period you want the equipment be grouped in the catalogue (choose only one) # (periods: 'monthly', 'annual') catalog_by_time = 'annual' +# Suffix for output file names +suffix_file_names = '__5y_20Kpop_10runs' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -96,12 +98,13 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): # %%% Set output file names # detailed CSV name - output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' + output_detailed_file_name = 'equipment_monthly_counts__all_event_details' + suffix_file_names + '.csv' # requested details only CSV name time_index = 'year' if catalog_by_time == 'annual' else 'date' output_focused_file_name = \ - 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' - output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id.csv' + 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + \ + suffix_file_names + '.csv' + output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id)' + suffix_file_names + '.csv' # --- # %% Catalog equipment by all HSI event details From 0a5d8e75aa27819b5c7043bc72f87ba7c76c5f72 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 22:39:32 +0000 Subject: [PATCH 227/443] co: equip udpated, PEP 8 --- src/tlo/methods/contraception.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index bdb4cecc83..4693ba495e 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1230,6 +1230,9 @@ 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.EQUIPMENT.update({'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine'}) + # Determine essential and optional items # TODO: we don't distinguish essential X optional for contraception methods yet, will need to update once we do items_essential = self.module.cons_codes[self.new_contraceptive] @@ -1283,13 +1286,14 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive # Update equipment - self.EQUIPMENT.update({'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine'}) if _new_contraceptive == 'female_sterilization': self.EQUIPMENT.update({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise', 'Examination couch' }) elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps', 'Examination couch'}) + self.EQUIPMENT.update({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps', 'Examination couch' + }) else: _new_contraceptive = "not_using" From 8fb6496a7cc49585aa1a9541940e24dbe6f05c38 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 22:39:54 +0000 Subject: [PATCH 228/443] equip_catalogue: typo --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 4506892e8b..3f5ee995ba 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -104,7 +104,7 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): output_focused_file_name = \ 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + \ suffix_file_names + '.csv' - output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id)' + suffix_file_names + '.csv' + output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id' + suffix_file_names + '.csv' # --- # %% Catalog equipment by all HSI event details From 51595fa4c53f46b5e3caecf425673f350f413fba Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 11 Dec 2023 08:43:36 +0000 Subject: [PATCH 229/443] updates to MNH modules and ALRI following comments --- src/tlo/methods/alri.py | 9 +++++---- src/tlo/methods/care_of_women_during_pregnancy.py | 6 ++++-- src/tlo/methods/labour.py | 7 ++++--- 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index d06a38601a..bdf930216b 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2656,11 +2656,11 @@ def _try_treatment(antibiotic_indicated: Tuple[str], oxygen_indicated: bool) -> # todo: should equipment only be logged if consumables are available? # If individual requires oxygen, update equipment - if oxygen_indicated: + if oxygen_provided: self.EQUIPMENT.update({'Oxygen cylinder, with regulator', 'Nasal Prongs'}) # If individual requires intravenous antibiotics, update equipment - if antibiotic_indicated in ('1st_line_IV_antibiotics', + if antibiotic_available in ('1st_line_IV_antibiotics', 'Benzylpenicillin_gentamicin_therapy_for_severe_pneumonia'): self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) @@ -2752,13 +2752,14 @@ def do_on_follow_up_following_treatment_failure(self): """Things to do for a patient who is having this HSI following a failure of an earlier treatment. A further drug will be used but this will have no effect on the chance of the person dying.""" - self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) - if self._has_staph_aureus(): _ = self._get_cons('2nd_line_Antibiotic_therapy_for_severe_staph_pneumonia') else: _ = self._get_cons('Ceftriaxone_therapy_for_severe_pneumonia') + if _: + self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + def apply(self, person_id, squeeze_factor): """Assess and attempt to treat the person.""" diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 96a4b69752..369be33383 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -2624,13 +2624,15 @@ def apply(self, person_id, squeeze_factor): self.module, self, self.module.item_codes_preg_consumables, core='post_abortion_care_core', optional='post_abortion_care_optional') - self.EQUIPMENT.update({'D&C set', 'Suction Curettage machine', 'Drip stand', 'Infusion pump'}) - # Check HCW availability to deliver surgical removal of retained products sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self.sim.modules['Labour'], sf='retained_prod', hsi_event=self) + # Update equipment if intervention can happen + if baseline_cons and sf_check: + self.EQUIPMENT.update({'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 diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index a216c7c996..aadf9976aa 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1865,10 +1865,11 @@ def refer_for_cs(): sf_check = pregnancy_helper_functions.check_emonc_signal_function_will_run(self, sf='avd', hsi_event=hsi_event) - # Update equipment - hsi_event.EQUIPMENT.update({'Delivery Forceps', 'Vacuum extractor'}) - if avail and sf_check: + + # Update equipment + hsi_event.EQUIPMENT.update({'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 From 77a09268fff038856d84b444d08c60f2180609e1 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 11 Dec 2023 13:32:00 +0000 Subject: [PATCH 230/443] RTI equipment first pass --- src/tlo/methods/rti.py | 49 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 9ec5adb9bc..9a9f5612d8 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3121,13 +3121,21 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_Imaging' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'DiagRadio': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() 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()): + # TODO: use xray package when available + # TODO: robbie did not log the 'xray consumable' here as done in other modules (Tb) + self.EQUIPMENT.update({'X-ray machine', 'X-ray viewer'}) + + elif 'Tomography' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): self.ACCEPTED_FACILITY_LEVEL = '3' + self.EQUIPMENT.update({'Computed Tomography (CT machine)', 'CT scanner accessories'}) def did_not_run(self, *args, **kwargs): pass @@ -3187,6 +3195,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 8}) + self.EQUIPMENT = set() p = module.parameters # Load the parameters used in this event @@ -3428,8 +3437,14 @@ def apply(self, person_id, squeeze_factor): # mortality percentage = 51.2 overall, 50% for TBI admission and 49% for hemorrhage # determine the number of ICU days used to treat patient - # TODO: Add general ICU equipment here? What form of organ support is offered? if df.loc[person_id, 'rt_ISS_score'] > self.hdu_cut_off_iss_score: + + # TODO: some general ICU equipment listed below? additional would need to be added dependent on severity + # of illness + self.EQUIPMENT.update({ + 'Patient monitor', 'Blood pressure machine', + 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope'}) + mean_icu_days = p['mean_icu_days'] sd_icu_days = p['sd_icu_days'] mean_tbi_icu_days = p['mean_tbi_icu_days'] @@ -3746,9 +3761,6 @@ def apply(self, person_id, squeeze_factor): return self.make_appt_footprint({}) get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name - self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Oxygen cylinder, with regulator', 'Nasal Prongs', - 'Patient monitor', 'Blood pressure machine', 'Pulse oximeter'}) - # TODO: find a more complete list of required consumables for adults if is_child: self.module.item_codes_for_consumables_required['shock_treatment_child'] = { @@ -3776,6 +3788,9 @@ 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.EQUIPMENT.update({'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({}) @@ -3876,6 +3891,9 @@ def apply(self, person_id, squeeze_factor): data=f"Fracture casts available for person %d's {fracturecastcounts + slingcounts} fractures, " f"{person_id}" ) + + self.EQUIPMENT.update({'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 @@ -3967,6 +3985,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_OpenFractureTreatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MinorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -4011,6 +4030,11 @@ 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" ) + + # TODO: open fractures are usually treated surgically so more equipment is likely required. + # Not immediately clear if these individuals are also scheduled to the surgical HSIs. will leave for now + self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + person = df.loc[person_id] # update the dataframe to show this person is recieving treatment df.loc[person_id, 'rt_med_int'] = True @@ -4080,6 +4104,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name @@ -4175,6 +4200,7 @@ def __init__(self, module, person_id): p = self.module.parameters self.prob_mild_burns = p['prob_mild_burns'] + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name @@ -4278,6 +4304,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_TetanusVaccine' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'EPI': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -4339,6 +4366,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -4641,6 +4669,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({}) + self.EQUIPMENT = set() p = self.module.parameters self.prob_perm_disability_with_treatment_severe_TBI = p['prob_perm_disability_with_treatment_severe_TBI'] @@ -4729,6 +4758,11 @@ 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' + + # TODO: not all surgeries conducted here are laparotomies however likely to have most appropriate equipment + # so should be fine for now + self.EQUIPMENT.update({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) + # ------------------------ Track permanent disabilities with treatment ------------------------------------- # --------------------------------- Perm disability from TBI ----------------------------------------------- codes = ['133', '133a', '133b', '133c', '133d', '134', '134a', '134b', '135'] @@ -4982,6 +5016,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_MinorSurgeries' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MinorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -5044,6 +5079,10 @@ 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: + # TODO: not all surgeries conducted here are laparotomies however likely to have most appropriate equipment + # so should be fine for now + self.EQUIPMENT.update({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) + # create a dictionary to store the recovery times for each injury in days minor_surg_recov_time_days = { '322': 180, From 7cd4fc35cab751147b0580afe309b40702415d0d Mon Sep 17 00:00:00 2001 From: joehcollins Date: Tue, 2 Jan 2024 11:45:16 +0000 Subject: [PATCH 231/443] start adding consumables to cancer --- src/tlo/methods/breast_cancer.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 57e8421194..c83b33d22d 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -32,6 +32,7 @@ def __init__(self, name=None, resourcefilepath=None): self.linear_models_for_progession_of_brc_status = dict() self.lm_onset_breast_lump_discernible = None self.daly_wts = dict() + self.item_codes_bladder_can = dict() INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'SymptomManager'} @@ -331,6 +332,24 @@ def initialise_population(self, population): # set date of palliative care being initiated: same as diagnosis (NB. future HSI will be scheduled for this) df.loc[select_for_care, "brc_date_palliative_care"] = df.loc[select_for_care, "brc_date_diagnosis"] + # TODO: add + # def get_and_store_bladder_cancer_item_codes(self): + # """ + # This function defines the required consumables for each intervention delivered during this module and stores + # them in a module level dictionary called within HSIs + # """ + # get_item_code_from_pkg = self.sim.modules['HealthSystem'].get_item_codes_from_package_name + # + # def get_list_of_items(item_list): + # item_code_function = self.sim.modules['HealthSystem'].get_item_code_from_item_name + # codes = [item_code_function(item) for item in item_list] + # return codes + # + # self.item_codes_bladder_can['screening'] + # self.item_codes_bladder_can['treatment'] + # self.item_codes_bladder_can['palliation'] + + def initialise_simulation(self, sim): """ * Schedule the main polling event @@ -646,6 +665,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = set() # TODO: Eva's dummy equipment example (not sure if it actually needs to be added and if it is in the RF) # {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} @@ -733,6 +753,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + self.EQUIPMENT = set() # ap_oct23 - I believe this will almost always be mastectomy surgery with chemotherapy, so I think for equipment # we just need the standard surgery equipment list. We may need to add radiotherapy when more available. @@ -802,6 +823,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + self.EQUIPMENT = set() # ap_oct23 - Eva, I'm not aware of any equipment needed here. Clinical guidelines do not specify what # checks or monitoring are indicated From bc726c28c97a339105b0b4dcaba1d907362ab1c8 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 12 Jan 2024 13:28:27 +0000 Subject: [PATCH 232/443] adding consumables for the cancer modules. consumables are defined in cancer_consumables.py to prevent repeating code blocks --- src/tlo/methods/bladder_cancer.py | 87 ++++++++++++------- src/tlo/methods/breast_cancer.py | 112 +++++++++++++------------ src/tlo/methods/cancer_consumables.py | 88 +++++++++++++++++++ src/tlo/methods/oesophagealcancer.py | 80 +++++++++++------- src/tlo/methods/other_adult_cancers.py | 78 ++++++++++------- src/tlo/methods/prostate_cancer.py | 92 +++++++++++++------- 6 files changed, 362 insertions(+), 175 deletions(-) create mode 100644 src/tlo/methods/cancer_consumables.py diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 300c7d486f..1a5a6fdfd7 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -13,7 +13,7 @@ from tlo import DateOffset, Module, Parameter, Property, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata +from tlo.methods import cancer_consumables, Metadata from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest @@ -34,6 +34,7 @@ def __init__(self, name=None, resourcefilepath=None): self.lm_onset_blood_urine = None self.lm_onset_pelvic_pain = None self.daly_wts = dict() + self.item_codes_bladder_can = dict() INIT_DEPENDENCIES = {'Demography', 'Lifestyle', 'HealthSystem', 'SymptomManager'} @@ -368,6 +369,9 @@ def initialise_simulation(self, sim): * Define the Disability-weights * Schedule the palliative care appointments for those that are on palliative care at initiation """ + # We call the following function to store the required consumables for the simulation run within the appropriate + # dictionary + cancer_consumables.get_consumable_item_codes_cancers(self, self.item_codes_bladder_can) # ----- SCHEDULE LOGGING EVENTS ----- # Schedule logging event to happen immediately @@ -681,7 +685,12 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - if dx_result: + # Check consumables are available + cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_cytoscopy_core'], + optional_item_codes= + self.module.item_codes_breast_can['screening_biopsy_optional']) + + if cons_avail and dx_result: # record date of diagnosis: df.at[person_id, 'bc_date_diagnosis'] = self.sim.date @@ -746,7 +755,12 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - if dx_result: + # Check consumables are available + cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_cytoscopy_core'], + optional_item_codes= + self.module.item_codes_breast_can['screening_biopsy_optional']) + + if cons_avail and dx_result: # record date of diagnosis: df.at[person_id, 'bc_date_diagnosis'] = self.sim.date @@ -823,20 +837,26 @@ def apply(self, person_id, squeeze_factor): assert not pd.isnull(df.at[person_id, "bc_date_diagnosis"]) assert pd.isnull(df.at[person_id, "bc_date_treatment"]) - # Record date and stage of starting treatment - df.at[person_id, "bc_date_treatment"] = self.sim.date - df.at[person_id, "bc_stage_at_which_treatment_given"] = df.at[person_id, "bc_status"] - - # Schedule a post-treatment check for 12 months: - hs.schedule_hsi_event( - hsi_event=HSI_BladderCancer_PostTreatmentCheck( - module=self.module, - person_id=person_id, - ), - topen=self.sim.date + DateOffset(years=12), - tclose=None, - priority=0 - ) + # Check consumables are available + cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['treatment_surgery_core'], + optional_item_codes= + self.module.item_codes_breast_can['treatment_surgery_optional']) + + if cons_avail: + # Record date and stage of starting treatment + df.at[person_id, "bc_date_treatment"] = self.sim.date + df.at[person_id, "bc_stage_at_which_treatment_given"] = df.at[person_id, "bc_status"] + + # Schedule a post-treatment check for 12 months: + hs.schedule_hsi_event( + hsi_event=HSI_BladderCancer_PostTreatmentCheck( + module=self.module, + person_id=person_id, + ), + topen=self.sim.date + DateOffset(years=12), + tclose=None, + priority=0 + ) class HSI_BladderCancer_PostTreatmentCheck(HSI_Event, IndividualScopeEventMixin): @@ -924,20 +944,25 @@ def apply(self, person_id, squeeze_factor): # Check that the person is in metastatic assert df.at[person_id, "bc_status"] == 'metastatic' - # Record the start of palliative care if this is first appointment - if pd.isnull(df.at[person_id, "bc_date_palliative_care"]): - df.at[person_id, "bc_date_palliative_care"] = self.sim.date - - # Schedule another instance of the event for one month - hs.schedule_hsi_event( - hsi_event=HSI_BladderCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - topen=self.sim.date + DateOffset(months=1), - tclose=None, - priority=0 - ) + # Check consumables are available + cons_available = self.get_consumables( + item_codes=self.module.item_codes_bladder_can['palliation']) + + if cons_available: + # Record the start of palliative care if this is first appointment + if pd.isnull(df.at[person_id, "bc_date_palliative_care"]): + df.at[person_id, "bc_date_palliative_care"] = self.sim.date + + # Schedule another instance of the event for one month + hs.schedule_hsi_event( + hsi_event=HSI_BladderCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + topen=self.sim.date + DateOffset(months=1), + tclose=None, + priority=0 + ) # --------------------------------------------------------------------------------------------------------- diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index c83b33d22d..a10e4eae82 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -12,7 +12,7 @@ from tlo import DateOffset, Module, Parameter, Property, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata +from tlo.methods import cancer_consumables, Metadata from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest @@ -32,7 +32,7 @@ def __init__(self, name=None, resourcefilepath=None): self.linear_models_for_progession_of_brc_status = dict() self.lm_onset_breast_lump_discernible = None self.daly_wts = dict() - self.item_codes_bladder_can = dict() + self.item_codes_breast_can = dict() INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'SymptomManager'} @@ -332,24 +332,6 @@ def initialise_population(self, population): # set date of palliative care being initiated: same as diagnosis (NB. future HSI will be scheduled for this) df.loc[select_for_care, "brc_date_palliative_care"] = df.loc[select_for_care, "brc_date_diagnosis"] - # TODO: add - # def get_and_store_bladder_cancer_item_codes(self): - # """ - # This function defines the required consumables for each intervention delivered during this module and stores - # them in a module level dictionary called within HSIs - # """ - # get_item_code_from_pkg = self.sim.modules['HealthSystem'].get_item_codes_from_package_name - # - # def get_list_of_items(item_list): - # item_code_function = self.sim.modules['HealthSystem'].get_item_code_from_item_name - # codes = [item_code_function(item) for item in item_list] - # return codes - # - # self.item_codes_bladder_can['screening'] - # self.item_codes_bladder_can['treatment'] - # self.item_codes_bladder_can['palliation'] - - def initialise_simulation(self, sim): """ * Schedule the main polling event @@ -359,6 +341,9 @@ def initialise_simulation(self, sim): * Define the Disability-weights * Schedule the palliative care appointments for those that are on palliative care at initiation """ + # We call the following function to store the required consumables for the simulation run within the appropriate + # dictionary + cancer_consumables.get_consumable_item_codes_cancers(self, self.item_codes_breast_can) # ----- SCHEDULE LOGGING EVENTS ----- # Schedule logging event to happen immediately @@ -695,14 +680,16 @@ def apply(self, person_id, squeeze_factor): df.brc_breast_lump_discernible_investigated = True # Use a biopsy to diagnose whether the person has breast Cancer: - # todo: request consumables needed for this + 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']) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', hsi_event=self ) - if dx_result: + if dx_result and cons_avail: # record date of diagnosis: df.at[person_id, 'brc_date_diagnosis'] = self.sim.date @@ -789,23 +776,35 @@ def apply(self, person_id, squeeze_factor): assert not pd.isnull(df.at[person_id, "brc_date_diagnosis"]) assert pd.isnull(df.at[person_id, "brc_date_treatment"]) - # Record date and stage of starting treatment - df.at[person_id, "brc_date_treatment"] = self.sim.date - df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - - # TODO: Eva's dummy equipment example - it needs to be replaced by real items from the RF - # Update equipment - # self.EQUIPMENT.update({'Anything used for mastectomy'}) - - # Schedule a post-treatment check for 12 months: - hs.schedule_hsi_event( - hsi_event=HSI_BreastCancer_PostTreatmentCheck( - module=self.module, - person_id=person_id, - ), - topen=self.sim.date + DateOffset(months=12), - tclose=None, - priority=0 + cons_available = self.get_consumables( + item_codes=self.module.item_codes_breast_can['treatment_surgery_core'], + optional_item_codes=self.module.item_codes_breast_can['treatment_surgery_optional'], + ) + + if cons_available: + + # Log the use of adjuvant chemotherapy + self.get_consumables( + item_codes=self.module.item_codes_breast_can['treatment_chemotherapy'], + optional_item_codes=self.module.item_codes_breast_can['iv_drug_cons']) + + # Record date and stage of starting treatment + df.at[person_id, "brc_date_treatment"] = self.sim.date + df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + + # TODO: Eva's dummy equipment example - it needs to be replaced by real items from the RF + # Update equipment + # self.EQUIPMENT.update({'Anything used for mastectomy'}) + + # Schedule a post-treatment check for 12 months: + hs.schedule_hsi_event( + hsi_event=HSI_BreastCancer_PostTreatmentCheck( + module=self.module, + person_id=person_id, + ), + topen=self.sim.date + DateOffset(months=12), + tclose=None, + priority=0 ) @@ -890,28 +889,31 @@ 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() # Check that the person is in stage4 assert df.at[person_id, "brc_status"] == 'stage4' - # Record the start of palliative care if this is first appointment - if pd.isnull(df.at[person_id, "brc_date_palliative_care"]): - df.at[person_id, "brc_date_palliative_care"] = self.sim.date - - # Schedule another instance of the event for one month - hs.schedule_hsi_event( - hsi_event=HSI_BreastCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - topen=self.sim.date + DateOffset(months=3), - tclose=None, - priority=0 - ) + # Check consumables are available + cons_available = self.get_consumables( + item_codes=self.module.item_codes_breast_can['palliation']) + + if cons_available: + # Record the start of palliative care if this is first appointment + if pd.isnull(df.at[person_id, "brc_date_palliative_care"]): + df.at[person_id, "brc_date_palliative_care"] = self.sim.date + + # Schedule another instance of the event for one month + hs.schedule_hsi_event( + hsi_event=HSI_BreastCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + topen=self.sim.date + DateOffset(months=3), + tclose=None, + priority=0 + ) # --------------------------------------------------------------------------------------------------------- diff --git a/src/tlo/methods/cancer_consumables.py b/src/tlo/methods/cancer_consumables.py new file mode 100644 index 0000000000..76d591f788 --- /dev/null +++ b/src/tlo/methods/cancer_consumables.py @@ -0,0 +1,88 @@ +""" +This file stores defines the consumables required within the cancer modules +""" + +import numpy as np +import pandas as pd + +from tlo import logging + + +def get_consumable_item_codes_cancers(self, cons_dict): + """ + This function stores the relevant item codes for cancer consumables across the five cancer modules to prevent + repetition within module code. + """ + + def get_list_of_items(item_list): + item_code_function = self.sim.modules['HealthSystem'].get_item_code_from_item_name + codes = [item_code_function(item) for item in item_list] + return codes + + # to do: add syringes, dressing + cons_dict['screening_biopsy_core'] = get_list_of_items(['Biopsy needle']) + + cons_dict['screening_biopsy_optional'] = \ + get_list_of_items(['Specimen container', + 'Lidocaine, injection, 1 % in 20 ml vial', + 'Gauze, absorbent 90cm x 40m_each_CMST', + 'Disposables gloves, powder free, 100 pieces per box']) + + cons_dict['treatment_surgery_core'] = \ + get_list_of_items(['Halothane (fluothane)_250ml_CMST', + 'Scalpel blade size 22 (individually wrapped)_100_CMST']) + + cons_dict['treatment_surgery_optional'] = \ + get_list_of_items(['Sodium chloride, injectable solution, 0,9 %, 500 ml', + 'Paracetamol, tablet, 500 mg', + 'Pethidine, 50 mg/ml, 2 ml ampoule', + 'Suture pack', + 'Gauze, absorbent 90cm x 40m_each_CMST', + 'Cannula iv (winged with injection pot) 18_each_CMST']) + + # This is not an exhaustive list of drugs required for palliation + cons_dict['palliation'] = \ + get_list_of_items(['morphine sulphate 10 mg/ml, 1 ml, injection (nt)_10_IDA', + 'Diazepam, injection, 5 mg/ml, in 2 ml ampoule', + ]) + + cons_dict['iv_drug_cons'] = \ + get_list_of_items(['Cannula iv (winged with injection pot) 18_each_CMST', + 'Giving set iv administration + needle 15 drops/ml_each_CMST', + 'Disposables gloves, powder free, 100 pieces per box' + ]) + + if self == self.sim.modules['BreastCancer']: + + # TODO: chemotharpy protocols??: TAC(Taxotere, Adriamycin, and Cyclophosphamide), AC (anthracycline and + # cyclophosphamide) +/-Taxane, TC (Taxotere and cyclophosphamide), CMF (cyclophosphamide, methotrexate, + # and fluorouracil), FEC-75 (5-Fluorouracil, Epirubicin, Cyclophosphamide). HER 2 +: Add Trastuzumab + + # only chemotherapy i consumable list which is also in suggested protocol is cyclo + cons_dict['treatment_chemotherapy'] = get_list_of_items(['Cyclophosphamide, 1 g']) + + elif self == self.sim.modules['ProstateCancer']: + + cons_dict['screening_psa_test_core'] = get_list_of_items(['Prostate specific antigen test']) + + cons_dict['screening_psa_test_optional'] = \ + get_list_of_items(['Blood collecting tube, 5 ml' + 'Disposables gloves, powder free, 100 pieces per box']) + + elif self == self.sim.modules['Bladder_cancer']: + # Note: bladder cancer is not in the malawi STG 2023 therefore no details on chemotherapy + + cons_dict['screening_cytoscopy_core'] = get_list_of_items(['Cytoscope']) + + cons_dict['screening_cytoscope_optional'] = get_list_of_items(['Specimen container']) + + elif self == self.sim.modules['OesophagealCancer']: + + cons_dict['screening_endoscope_core'] = get_list_of_items(['Endoscope']) + + cons_dict['screening_endoscope_optional'] =\ + get_list_of_items(['Specimen container', + 'Gauze, absorbent 90cm x 40m_each_CMST']) + + cons_dict['treatment_chemotherapy'] = get_list_of_items(['Cisplatin 50mg Injection']) + diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index e8714176ec..7f4a9ee15f 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -14,7 +14,7 @@ from tlo import DateOffset, Module, Parameter, Property, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata +from tlo.methods import cancer_consumables, Metadata from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest @@ -34,6 +34,7 @@ def __init__(self, name=None, resourcefilepath=None): self.linear_models_for_progession_of_oc_status = dict() self.lm_onset_dysphagia = None self.daly_wts = dict() + self.item_codes_oesophageal_can = dict() INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'Lifestyle', 'SymptomManager'} @@ -355,6 +356,9 @@ def initialise_simulation(self, sim): * Define the Disability-weights * Schedule the palliative care appointments for those that are on palliative care at initiation """ + # We call the following function to store the required consumables for the simulation run within the appropriate + # dictionary + cancer_consumables.get_consumable_item_codes_cancers(self, self.item_codes_oesophageal_can) # ----- SCHEDULE LOGGING EVENTS ----- # Schedule logging event to happen immediately @@ -667,8 +671,11 @@ def apply(self, person_id, squeeze_factor): dx_tests_to_run='endoscopy_for_oes_cancer_given_dysphagia', hsi_event=self ) + cons_avail = self.get_consumables(item_codes=self.module.item_codes_oesophageal_can['screening_endoscope_core'], + optional_item_codes= + self.module.item_codes_oesophageal_can['screening_endoscope_optional']) - if dx_result: + if dx_result and cons_avail: # record date of diagnosis: df.at[person_id, 'oc_date_diagnosis'] = self.sim.date @@ -745,20 +752,32 @@ def apply(self, person_id, squeeze_factor): assert not pd.isnull(df.at[person_id, "oc_date_diagnosis"]) assert pd.isnull(df.at[person_id, "oc_date_treatment"]) - # Record date and stage of starting treatment - df.at[person_id, "oc_date_treatment"] = self.sim.date - df.at[person_id, "oc_stage_at_which_treatment_applied"] = df.at[person_id, "oc_status"] + # Check consumables are available + cons_avail = self.get_consumables(item_codes=self.module.item_codes_oesophageal_can['treatment_surgery_core'], + optional_item_codes= + self.module.item_codes_oesophageal_can['treatment_surgery_optional']) - # Schedule a post-treatment check for 12 months: - hs.schedule_hsi_event( - hsi_event=HSI_OesophagealCancer_PostTreatmentCheck( - module=self.module, - person_id=person_id, - ), - topen=self.sim.date + DateOffset(years=12), - tclose=None, - priority=0 - ) + if cons_avail: + + # Log chemotherapy consumables + self.get_consumables( + item_codes=self.module.item_codes_oesophageal_can['treatment_chemotherapy'], + optional_item_codes=self.module.item_codes_oesophageal_can['iv_drug_cons']) + + # Record date and stage of starting treatment + df.at[person_id, "oc_date_treatment"] = self.sim.date + df.at[person_id, "oc_stage_at_which_treatment_applied"] = df.at[person_id, "oc_status"] + + # Schedule a post-treatment check for 12 months: + hs.schedule_hsi_event( + hsi_event=HSI_OesophagealCancer_PostTreatmentCheck( + module=self.module, + person_id=person_id, + ), + topen=self.sim.date + DateOffset(years=12), + tclose=None, + priority=0 + ) class HSI_OesophagealCancer_PostTreatmentCheck(HSI_Event, IndividualScopeEventMixin): @@ -848,20 +867,25 @@ def apply(self, person_id, squeeze_factor): # Check that the person is in stage4 assert df.at[person_id, "oc_status"] == 'stage4' - # Record the start of palliative care if this is first appointment - if pd.isnull(df.at[person_id, "oc_date_palliative_care"]): - df.at[person_id, "oc_date_palliative_care"] = self.sim.date + # Check consumables are available + cons_available = self.get_consumables( + item_codes=self.module.item_codes_oesophageal_can['palliation']) - # Schedule another instance of the event for one month - hs.schedule_hsi_event( - hsi_event=HSI_OesophagealCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - topen=self.sim.date + DateOffset(months=1), - tclose=None, - priority=0 - ) + if cons_available: + # Record the start of palliative care if this is first appointment + if pd.isnull(df.at[person_id, "oc_date_palliative_care"]): + df.at[person_id, "oc_date_palliative_care"] = self.sim.date + + # Schedule another instance of the event for one month + hs.schedule_hsi_event( + hsi_event=HSI_OesophagealCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + topen=self.sim.date + DateOffset(months=1), + tclose=None, + priority=0 + ) # --------------------------------------------------------------------------------------------------------- diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 1b5828af62..2df95a79e7 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -12,7 +12,7 @@ from tlo import DateOffset, Module, Parameter, Property, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata +from tlo.methods import cancer_consumables, Metadata from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest @@ -32,6 +32,7 @@ def __init__(self, name=None, resourcefilepath=None): self.linear_models_for_progession_of_oac_status = dict() self.lm_onset_early_other_adult_ca_symptom = None self.daly_wts = dict() + self.item_codes_other_can = dict() INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'SymptomManager'} @@ -363,6 +364,9 @@ def initialise_simulation(self, sim): * Define the Disability-weights * Schedule the palliative care appointments for those that are on palliative care at initiation """ + # We call the following function to store the required consumables for the simulation run within the appropriate + # dictionary + cancer_consumables.get_consumable_item_codes_cancers(self, self.item_codes_other_can) # ----- SCHEDULE LOGGING EVENTS ----- # Schedule logging event to happen immediately @@ -659,7 +663,12 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - if dx_result: + # 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']) + + if dx_result and cons_avail: # record date of diagnosis: df.at[person_id, 'oac_date_diagnosis'] = self.sim.date @@ -736,21 +745,27 @@ def apply(self, person_id, squeeze_factor): assert not pd.isnull(df.at[person_id, "oac_date_diagnosis"]) assert pd.isnull(df.at[person_id, "oac_date_treatment"]) - # Record date and stage of starting treatment - df.at[person_id, "oac_date_treatment"] = self.sim.date - df.at[person_id, "oac_stage_at_which_treatment_given"] = df.at[person_id, "oac_status"] - - # Schedule a post-treatment check for 12 months: - hs.schedule_hsi_event( - hsi_event=HSI_OtherAdultCancer_PostTreatmentCheck( - module=self.module, - person_id=person_id, - ), - topen=self.sim.date + DateOffset(months=3), - tclose=None, - priority=0 + cons_available = self.get_consumables( + item_codes=self.module.item_codes_other_can['treatment_surgery_core'], + optional_item_codes=self.module.item_codes_other_can['treatment_surgery_optional'], ) + if cons_available: + # Record date and stage of starting treatment + df.at[person_id, "oac_date_treatment"] = self.sim.date + df.at[person_id, "oac_stage_at_which_treatment_given"] = df.at[person_id, "oac_status"] + + # Schedule a post-treatment check for 12 months: + hs.schedule_hsi_event( + hsi_event=HSI_OtherAdultCancer_PostTreatmentCheck( + module=self.module, + person_id=person_id, + ), + topen=self.sim.date + DateOffset(months=3), + tclose=None, + priority=0 + ) + def did_not_run(self): pass @@ -844,20 +859,25 @@ def apply(self, person_id, squeeze_factor): # Check that the person is in metastatic assert df.at[person_id, "oac_status"] == 'metastatic' - # Record the start of palliative care if this is first appointment - if pd.isnull(df.at[person_id, "oac_date_palliative_care"]): - df.at[person_id, "oac_date_palliative_care"] = self.sim.date - - # Schedule another instance of the event for one month - hs.schedule_hsi_event( - hsi_event=HSI_OtherAdultCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - topen=self.sim.date + DateOffset(months=1), - tclose=None, - priority=0 - ) + # Check consumables are available + cons_available = self.get_consumables( + item_codes=self.module.item_codes_other_can['palliation']) + + if cons_available: + # Record the start of palliative care if this is first appointment + if pd.isnull(df.at[person_id, "oac_date_palliative_care"]): + df.at[person_id, "oac_date_palliative_care"] = self.sim.date + + # Schedule another instance of the event for one month + hs.schedule_hsi_event( + hsi_event=HSI_OtherAdultCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + topen=self.sim.date + DateOffset(months=1), + tclose=None, + priority=0 + ) def did_not_run(self): pass diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 95425eba9f..249f6be1bd 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -12,7 +12,7 @@ from tlo import DateOffset, Module, Parameter, Property, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata +from tlo.methods import cancer_consumables, Metadata from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest @@ -33,6 +33,7 @@ def __init__(self, name=None, resourcefilepath=None): self.lm_prostate_ca_onset_urinary_symptoms = None self.lm_onset_pelvic_pain = None self.daly_wts = dict() + self.item_codes_prostate_can = dict() INIT_DEPENDENCIES = {'Demography', 'HealthSystem', 'SymptomManager'} @@ -368,6 +369,9 @@ def initialise_simulation(self, sim): * Define the Disability-weights * Schedule the palliative care appointments for those that are on palliative care at initiation """ + # We call the following function to store the required consumables for the simulation run within the appropriate + # dictionary + cancer_consumables.get_consumable_item_codes_cancers(self, self.item_codes_prostate_can) # ----- SCHEDULE LOGGING EVENTS ----- # Schedule logging event to happen immediately @@ -697,7 +701,12 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - if dx_result: + # Check consumable availability + cons_avail = self.get_consumables(item_codes=self.module.item_codes_prostate_can['screening_psa_core'], + optional_item_codes=self.module.item_codes_prostate_can[ + 'screening_psa_optional']) + + if dx_result and cons_avail: # send for biopsy hs.schedule_hsi_event( hsi_event=HSI_ProstateCancer_Investigation_Following_psa_positive( @@ -744,7 +753,12 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - if dx_result: + # Check consumable availability + cons_avail = self.get_consumables(item_codes=self.module.item_codes_prostate_can['screening_psa_core'], + optional_item_codes=self.module.item_codes_prostate_can[ + 'screening_psa_optional']) + + if dx_result and cons_avail: # send for biopsy hs.schedule_hsi_event( hsi_event=HSI_ProstateCancer_Investigation_Following_psa_positive( @@ -788,7 +802,11 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - if dx_result: + 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']) + + if dx_result and cons_available: # record date of diagnosis: df.at[person_id, 'pc_date_diagnosis'] = self.sim.date @@ -866,20 +884,25 @@ def apply(self, person_id, squeeze_factor): assert not pd.isnull(df.at[person_id, "pc_date_diagnosis"]) assert pd.isnull(df.at[person_id, "pc_date_treatment"]) - # Record date and stage of starting treatment - df.at[person_id, "pc_date_treatment"] = self.sim.date - df.at[person_id, "pc_stage_at_which_treatment_given"] = df.at[person_id, "pc_status"] - - # Schedule a post-treatment check for 12 months: - hs.schedule_hsi_event( - hsi_event=HSI_ProstateCancer_PostTreatmentCheck( - module=self.module, - person_id=person_id, - ), - topen=self.sim.date + DateOffset(months=12), - tclose=None, - priority=0 - ) + cons_available = self.get_consumables(item_codes=self.module.item_codes_prostate_can['treatment_surgery_core'], + optional_item_codes=self.module.item_codes_prostate_can[ + 'treatment_surgery_optional']) + + if cons_available: + # Record date and stage of starting treatment + df.at[person_id, "pc_date_treatment"] = self.sim.date + df.at[person_id, "pc_stage_at_which_treatment_given"] = df.at[person_id, "pc_status"] + + # Schedule a post-treatment check for 12 months: + hs.schedule_hsi_event( + hsi_event=HSI_ProstateCancer_PostTreatmentCheck( + module=self.module, + person_id=person_id, + ), + topen=self.sim.date + DateOffset(months=12), + tclose=None, + priority=0 + ) class HSI_ProstateCancer_PostTreatmentCheck(HSI_Event, IndividualScopeEventMixin): @@ -967,20 +990,25 @@ def apply(self, person_id, squeeze_factor): # Check that the person is in metastatic assert df.at[person_id, "pc_status"] == 'metastatic' - # Record the start of palliative care if this is first appointment - if pd.isnull(df.at[person_id, "pc_date_palliative_care"]): - df.at[person_id, "pc_date_palliative_care"] = self.sim.date - - # Schedule another instance of the event for one month - hs.schedule_hsi_event( - hsi_event=HSI_ProstateCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - topen=self.sim.date + DateOffset(months=1), - tclose=None, - priority=0 - ) + # Check consumables are available + cons_available = self.get_consumables( + item_codes=self.module.item_codes_prostate_can['palliation']) + + if cons_available: + # Record the start of palliative care if this is first appointment + if pd.isnull(df.at[person_id, "pc_date_palliative_care"]): + df.at[person_id, "pc_date_palliative_care"] = self.sim.date + + # Schedule another instance of the event for one month + hs.schedule_hsi_event( + hsi_event=HSI_ProstateCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + topen=self.sim.date + DateOffset(months=1), + tclose=None, + priority=0 + ) # --------------------------------------------------------------------------------------------------------- From 9889da9db87220a67f1070da0c21f909b4d48ab8 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 12 Jan 2024 14:57:59 +0000 Subject: [PATCH 233/443] adding equipment (first pass) to cancer modules. In addition have updated spelling of cystoscopy within bladder_cancer.py and the associated resource file. --- resources/ResourceFile_Bladder_Cancer.xlsx | 2 +- src/tlo/methods/bladder_cancer.py | 192 ++++++++++++--------- src/tlo/methods/breast_cancer.py | 85 +++++---- src/tlo/methods/cancer_consumables.py | 4 +- src/tlo/methods/oesophagealcancer.py | 89 ++++++---- src/tlo/methods/other_adult_cancers.py | 91 ++++++---- src/tlo/methods/prostate_cancer.py | 87 ++++++---- 7 files changed, 319 insertions(+), 231 deletions(-) diff --git a/resources/ResourceFile_Bladder_Cancer.xlsx b/resources/ResourceFile_Bladder_Cancer.xlsx index f6b7290213..74e26b31a8 100644 --- a/resources/ResourceFile_Bladder_Cancer.xlsx +++ b/resources/ResourceFile_Bladder_Cancer.xlsx @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0801d6c43263854111fa13779db68c2c426bd99f517860cad73bbbee2e4b3334 +oid sha256:7ae80b76d054f9223f15e5a592189bd732a397810192990bfa63fb3dec393fb5 size 10954 diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 1a5a6fdfd7..782e4f83f3 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -2,7 +2,7 @@ Bladder Cancer Disease Module Limitations to note: -* Needs to represent the the DxTest 'cytoscopy_blood_urine_bladder_cancer' requires use of a cytoscope +* Needs to represent the the DxTest 'cystoscopy_blood_urine_bladder_cancer' requires use of a cystoscope * Footprints of HSI -- pending input from expert on resources required. """ @@ -157,11 +157,11 @@ def __init__(self, name=None, resourcefilepath=None): "rp_bladder_cancer_schisto_h": Parameter( Types.REAL, "relative prevalence at baseline of bladder cancer if schisto_h" ), - "sensitivity_of_cytoscopy_for_bladder_cancer_blood_urine": Parameter( - Types.REAL, "sensitivity of cytoscopy_for diagnosis of bladder cancer given blood urine" + "sensitivity_of_cystoscopy_for_bladder_cancer_blood_urine": Parameter( + Types.REAL, "sensitivity of cystoscopy_for diagnosis of bladder cancer given blood urine" ), - "sensitivity_of_cytoscopy_for_bladder_cancer_pelvic_pain": Parameter( - Types.REAL, "sensitivity of cytoscopy_for diagnosis of bladder cancer given pelvic pain" + "sensitivity_of_cystoscopy_for_bladder_cancer_pelvic_pain": Parameter( + Types.REAL, "sensitivity of cystoscopy_for diagnosis of bladder cancer given pelvic pain" ) } @@ -462,17 +462,17 @@ def initialise_simulation(self, sim): # This properties of conditional on the test being done only to persons with the Symptom, 'blood_urine'. self.sim.modules['HealthSystem'].dx_manager.register_dx_test( - cytoscopy_for_bladder_cancer_given_blood_urine=DxTest( + cystoscopy_for_bladder_cancer_given_blood_urine=DxTest( property='bc_status', - sensitivity=self.parameters['sensitivity_of_cytoscopy_for_bladder_cancer_blood_urine'], + sensitivity=self.parameters['sensitivity_of_cystoscopy_for_bladder_cancer_blood_urine'], target_categories=["tis_t1", "t2p", "metastatic"] ) ) self.sim.modules['HealthSystem'].dx_manager.register_dx_test( - cytoscopy_for_bladder_cancer_given_pelvic_pain=DxTest( + cystoscopy_for_bladder_cancer_given_pelvic_pain=DxTest( property='bc_status', - sensitivity=self.parameters['sensitivity_of_cytoscopy_for_bladder_cancer_pelvic_pain'], + sensitivity=self.parameters['sensitivity_of_cystoscopy_for_bladder_cancer_pelvic_pain'], target_categories=["tis_t1", "t2p", "metastatic"] ) ) @@ -661,6 +661,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() # equipment: (ultrsound guided) biopsy, lab equipment for histology @@ -679,48 +680,53 @@ def apply(self, person_id, squeeze_factor): if not pd.isnull(df.at[person_id, "bc_date_diagnosis"]): return hs.get_blank_appt_footprint() - # Use a cytoscope to diagnose whether the person has bladder Cancer: - dx_result = hs.dx_manager.run_dx_test( - dx_tests_to_run='cytoscopy_for_bladder_cancer_given_blood_urine', - hsi_event=self - ) - # Check consumables are available - cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_cytoscopy_core'], + cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_cystoscopy_core'], optional_item_codes= self.module.item_codes_breast_can['screening_biopsy_optional']) - if cons_avail and dx_result: - # record date of diagnosis: - df.at[person_id, 'bc_date_diagnosis'] = self.sim.date - - # Check if is in metastatic: - in_metastatic = df.at[person_id, 'bc_status'] == 'metastatic' - - # If diagnosis detects cancer, we assume classification as metastatic is accurate - if not in_metastatic: - # start treatment: - hs.schedule_hsi_event( - hsi_event=HSI_BladderCancer_StartTreatment( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) + if cons_avail: + # Use a biopsy to diagnose whether the person has breast Cancer + # If consumables are available update the use of equipment and run the dx_test representing the biopsy + self.EQUIPMENT.update({'Cystoscope', 'Ordinary Microscope'}) + + # Use a cystoscope to diagnose whether the person has bladder Cancer: + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='cystoscopy_for_bladder_cancer_given_blood_urine', + hsi_event=self + ) - else: - # start palliative care: - hs.schedule_hsi_event( - hsi_event=HSI_BladderCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) + if dx_result: + # record date of diagnosis: + df.at[person_id, 'bc_date_diagnosis'] = self.sim.date + + # Check if is in metastatic: + in_metastatic = df.at[person_id, 'bc_status'] == 'metastatic' + + # If diagnosis detects cancer, we assume classification as metastatic is accurate + if not in_metastatic: + # start treatment: + hs.schedule_hsi_event( + hsi_event=HSI_BladderCancer_StartTreatment( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) + + else: + # start palliative care: + hs.schedule_hsi_event( + hsi_event=HSI_BladderCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) class HSI_BladderCancer_Investigation_Following_pelvic_pain(HSI_Event, IndividualScopeEventMixin): @@ -731,6 +737,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() # equipment: (ultrsound guided) biopsy, lab equipment for histology @@ -749,54 +756,58 @@ def apply(self, person_id, squeeze_factor): if not pd.isnull(df.at[person_id, "bc_date_diagnosis"]): return hs.get_blank_appt_footprint() - # Use a cytoscope to diagnose whether the person has bladder Cancer: - dx_result = hs.dx_manager.run_dx_test( - dx_tests_to_run='cytoscopy_for_bladder_cancer_given_pelvic_pain', - hsi_event=self - ) - # Check consumables are available - cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_cytoscopy_core'], + cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_cystoscopy_core'], optional_item_codes= self.module.item_codes_breast_can['screening_biopsy_optional']) + if cons_avail: + # Use a biopsy to diagnose whether the person has breast Cancer + # If consumables are available log the use of equipment and run the dx_test representing the biopsy + self.EQUIPMENT.update({'Cystoscope', 'Ordinary Microscope'}) + + # Use a cystoscope to diagnose whether the person has bladder Cancer: + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='cystoscopy_for_bladder_cancer_given_pelvic_pain', + hsi_event=self + ) - if cons_avail and dx_result: - # record date of diagnosis: - df.at[person_id, 'bc_date_diagnosis'] = self.sim.date - - # Check if is in metastatic: - in_metastatic = df.at[person_id, 'bc_status'] == 'metastatic' - - # If diagnosis detects cancer, we assume classification as metastatic is accurate - if not in_metastatic: - # start treatment: - hs.schedule_hsi_event( - hsi_event=HSI_BladderCancer_StartTreatment( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) - - else: - # start palliative care: - hs.schedule_hsi_event( - hsi_event=HSI_BladderCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) + if dx_result: + # record date of diagnosis: + df.at[person_id, 'bc_date_diagnosis'] = self.sim.date + + # Check if is in metastatic: + in_metastatic = df.at[person_id, 'bc_status'] == 'metastatic' + + # If diagnosis detects cancer, we assume classification as metastatic is accurate + if not in_metastatic: + # start treatment: + hs.schedule_hsi_event( + hsi_event=HSI_BladderCancer_StartTreatment( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) + + else: + # start palliative care: + hs.schedule_hsi_event( + hsi_event=HSI_BladderCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) class HSI_BladderCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): """ Scheduled by HSI_bladderCancer_Investigation_Following_blood_urine or pelvic pain following a - diagnosis of bladder Cancer using cytoscopy. It initiates the treatment of bladder Cancer. + diagnosis of bladder Cancer using cystoscopy. It initiates the treatment of bladder Cancer. It is only for persons with a cancer that is not in metastatic and who have been diagnosed. """ def __init__(self, module, person_id): @@ -806,6 +817,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) + self.EQUIPMENT = set() # equipment: standard equipment for surgery @@ -843,6 +855,10 @@ def apply(self, person_id, squeeze_factor): self.module.item_codes_breast_can['treatment_surgery_optional']) if cons_avail: + # If consumables are available and the treatment will go ahead - update the equipment + self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', + 'Blood pressure machine', 'Pulse oximeter'}) + # Record date and stage of starting treatment df.at[person_id, "bc_date_treatment"] = self.sim.date df.at[person_id, "bc_stage_at_which_treatment_given"] = df.at[person_id, "bc_status"] @@ -873,6 +889,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + self.EQUIPMENT = set() # I assume ultrasound (Ultrasound scanning machine) and biopsy @@ -931,6 +948,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + self.EQUIPMENT = set() # no equipment as far as I am aware @@ -949,6 +967,10 @@ 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 - update the equipment + self.EQUIPMENT.update({'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"]): df.at[person_id, "bc_date_palliative_care"] = self.sim.date diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index a10e4eae82..1c461a5b6b 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -679,47 +679,53 @@ def apply(self, person_id, squeeze_factor): df.brc_breast_lump_discernible_investigated = True - # Use a biopsy to diagnose whether the person has breast Cancer: + # 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']) - dx_result = hs.dx_manager.run_dx_test( - dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', - hsi_event=self - ) + if cons_avail: + # Use a biopsy to diagnose whether the person has breast Cancer + # If consumables are available update the use of equipment and run the dx_test representing the biopsy + self.EQUIPMENT.update({'Ultrasound scanning machine', 'Ordinary Microscope'}) - if dx_result and cons_avail: - # record date of diagnosis: - df.at[person_id, 'brc_date_diagnosis'] = self.sim.date - - # Check if is in stage4: - in_stage4 = df.at[person_id, 'brc_status'] == 'stage4' - # If the diagnosis does detect cancer, it is assumed that the classification as stage4 is made accurately. - - if not in_stage4: - # start treatment: - hs.schedule_hsi_event( - hsi_event=HSI_BreastCancer_StartTreatment( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', + hsi_event=self + ) - else: - # start palliative care: - hs.schedule_hsi_event( - hsi_event=HSI_BreastCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) + if dx_result: + # record date of diagnosis: + df.at[person_id, 'brc_date_diagnosis'] = self.sim.date + + # Check if is in stage4: + in_stage4 = df.at[person_id, 'brc_status'] == 'stage4' + # If the diagnosis does detect cancer, it is assumed that the classification as stage4 is + # made accurately. + + if not in_stage4: + # start treatment: + hs.schedule_hsi_event( + hsi_event=HSI_BreastCancer_StartTreatment( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) + + else: + # start palliative care: + hs.schedule_hsi_event( + hsi_event=HSI_BreastCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) # todo: we would like to note that the symptom has been investigated in a diagnostic test and the diagnosis was # todo: was missed, so the same test will not likely be repeated, at least not in the short term, so we even @@ -776,12 +782,16 @@ def apply(self, person_id, squeeze_factor): assert not pd.isnull(df.at[person_id, "brc_date_diagnosis"]) assert pd.isnull(df.at[person_id, "brc_date_treatment"]) + # Check that consumables are available cons_available = self.get_consumables( item_codes=self.module.item_codes_breast_can['treatment_surgery_core'], optional_item_codes=self.module.item_codes_breast_can['treatment_surgery_optional'], ) if cons_available: + # If consumables are available and the treatment will go ahead - update the equipment + self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', + 'Blood pressure machine', 'Pulse oximeter'}) # Log the use of adjuvant chemotherapy self.get_consumables( @@ -882,6 +892,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + self.EQUIPMENT = set() # not sure there is any need for equipment here @@ -900,6 +911,10 @@ 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 - update the equipment + self.EQUIPMENT.update({'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"]): df.at[person_id, "brc_date_palliative_care"] = self.sim.date diff --git a/src/tlo/methods/cancer_consumables.py b/src/tlo/methods/cancer_consumables.py index 76d591f788..1a44afd82c 100644 --- a/src/tlo/methods/cancer_consumables.py +++ b/src/tlo/methods/cancer_consumables.py @@ -72,9 +72,9 @@ def get_list_of_items(item_list): elif self == self.sim.modules['Bladder_cancer']: # Note: bladder cancer is not in the malawi STG 2023 therefore no details on chemotherapy - cons_dict['screening_cytoscopy_core'] = get_list_of_items(['Cytoscope']) + cons_dict['screening_cystoscopy_core'] = get_list_of_items(['Cytoscope']) - cons_dict['screening_cytoscope_optional'] = get_list_of_items(['Specimen container']) + cons_dict['screening_cystoscope_optional'] = get_list_of_items(['Specimen container']) elif self == self.sim.modules['OesophagealCancer']: diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index 7f4a9ee15f..0c2250a388 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -647,6 +647,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OesophagealCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() # I think this will need endoscope and biopsy needle. Also lab equipment needed to perform histology. # I can't see endoscope in equipment list but it may be given a slightly different name @@ -666,46 +667,53 @@ def apply(self, person_id, squeeze_factor): if not pd.isnull(df.at[person_id, "oc_date_diagnosis"]): return hs.get_blank_appt_footprint() - # Use an endoscope to diagnose whether the person has Oesophageal Cancer: - dx_result = hs.dx_manager.run_dx_test( - dx_tests_to_run='endoscopy_for_oes_cancer_given_dysphagia', - hsi_event=self - ) + # Check the consumables are available cons_avail = self.get_consumables(item_codes=self.module.item_codes_oesophageal_can['screening_endoscope_core'], optional_item_codes= self.module.item_codes_oesophageal_can['screening_endoscope_optional']) - if dx_result and cons_avail: - # record date of diagnosis: - df.at[person_id, 'oc_date_diagnosis'] = self.sim.date - - # Check if is in stage4: - in_stage4 = df.at[person_id, 'oc_status'] == 'stage4' - # If the diagnosis does detect cancer, it is assumed that the classification as stage4 is made accurately. - - if not in_stage4: - # start treatment: - hs.schedule_hsi_event( - hsi_event=HSI_OesophagealCancer_StartTreatment( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) - - else: - # start palliative care: - hs.schedule_hsi_event( - hsi_event=HSI_OesophagealCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) + if cons_avail: + # If consumables are available update equipment and run the dx_test representing the biopsy + # n.b. endoscope not in equipment list + self.EQUIPMENT.update({'Ordinary Microscope'}) + + # Use an endoscope to diagnose whether the person has Oesophageal Cancer: + dx_result = hs.dx_manager.run_dx_test( + dx_tests_to_run='endoscopy_for_oes_cancer_given_dysphagia', + hsi_event=self + ) + if dx_result: + # record date of diagnosis: + df.at[person_id, 'oc_date_diagnosis'] = self.sim.date + + # Check if is in stage4: + in_stage4 = df.at[person_id, 'oc_status'] == 'stage4' + # If the diagnosis does detect cancer, it is assumed that the classification as stage4 is made + # accurately. + + if not in_stage4: + # start treatment: + hs.schedule_hsi_event( + hsi_event=HSI_OesophagealCancer_StartTreatment( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) + + else: + # start palliative care: + hs.schedule_hsi_event( + hsi_event=HSI_OesophagealCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) class HSI_OesophagealCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): @@ -722,6 +730,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + self.EQUIPMENT = set() # equipment need here will be surgery @@ -758,6 +767,9 @@ 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 - update the equipment + self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', + 'Blood pressure machine', 'Pulse oximeter'}) # Log chemotherapy consumables self.get_consumables( @@ -794,6 +806,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OesophagealCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + self.EQUIPMENT = set() # equipment: I assume endoscope needed for this @@ -852,6 +865,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + self.EQUIPMENT = set() # when radiology available then palliative radiology may be performed but suggest we don't need to include yet # not sure what equipment needed for Endoscopic stent placement or Feeding tube which are done as palliative @@ -872,6 +886,9 @@ 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 - update the equipment + self.EQUIPMENT.update({'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"]): df.at[person_id, "oc_date_palliative_care"] = self.sim.date diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 2df95a79e7..0b6ed4ad0b 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -638,6 +638,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OtherAdultCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() # equipment: investigations will differ by presenting symptom, but suggest we have biopsy and histology # and ultrasound @@ -657,49 +658,54 @@ def apply(self, person_id, squeeze_factor): if not pd.isnull(df.at[person_id, "oac_date_diagnosis"]): return hs.get_blank_appt_footprint() - # Use a diagnostic_device to diagnose whether the person has other adult cancer: - dx_result = hs.dx_manager.run_dx_test( - dx_tests_to_run='diagnostic_device_for_other_adult_cancer_given_other_adult_ca_symptom', - hsi_event=self - ) - # 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']) - if dx_result and cons_avail: - # record date of diagnosis: - df.at[person_id, 'oac_date_diagnosis'] = self.sim.date - - # Check if is in metastatic: - in_metastatic = df.at[person_id, 'oac_status'] == 'metastatic' - # If the diagnosis does detect cancer, it is assumed that the classification as metastatic is - # made accurately. - - if not in_metastatic: - # start treatment: - hs.schedule_hsi_event( - hsi_event=HSI_OtherAdultCancer_StartTreatment( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) + if cons_avail: + # If consumables are available update equipment and run the dx_test representing the biopsy + self.EQUIPMENT.update({'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( + dx_tests_to_run='diagnostic_device_for_other_adult_cancer_given_other_adult_ca_symptom', + hsi_event=self + ) + + if dx_result: + # record date of diagnosis: + df.at[person_id, 'oac_date_diagnosis'] = self.sim.date + + # Check if is in metastatic: + in_metastatic = df.at[person_id, 'oac_status'] == 'metastatic' + # If the diagnosis does detect cancer, it is assumed that the classification as metastatic is + # made accurately. + + if not in_metastatic: + # start treatment: + hs.schedule_hsi_event( + hsi_event=HSI_OtherAdultCancer_StartTreatment( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) + + else: + # start palliative care: + hs.schedule_hsi_event( + hsi_event=HSI_OtherAdultCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) - else: - # start palliative care: - hs.schedule_hsi_event( - hsi_event=HSI_OtherAdultCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) class HSI_OtherAdultCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): @@ -716,6 +722,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + self.EQUIPMENT = set() # equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available @@ -751,6 +758,10 @@ def apply(self, person_id, squeeze_factor): ) if cons_available: + # If consumables are available and the treatment will go ahead - update the equipment + self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', + 'Blood pressure machine', 'Pulse oximeter'}) + # Record date and stage of starting treatment df.at[person_id, "oac_date_treatment"] = self.sim.date df.at[person_id, "oac_stage_at_which_treatment_given"] = df.at[person_id, "oac_status"] @@ -784,6 +795,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OtherAdultCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + self.EQUIPMENT = set() # equipment: some checks will involve further biopsy, ultrasound, histology @@ -846,7 +858,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - + self.EQUIPMENT = set() # equipment: in general not required I don't think def apply(self, person_id, squeeze_factor): @@ -864,6 +876,9 @@ 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 - update the equipment + self.EQUIPMENT.update({'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"]): df.at[person_id, "oac_date_palliative_care"] = self.sim.date diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 249f6be1bd..ea831b35ac 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -674,6 +674,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. @@ -726,6 +727,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. @@ -778,6 +780,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' + self.EQUIPMENT = set() # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. @@ -796,47 +799,52 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'pc_date_biopsy'] = self.sim.date # todo: stratify by pc_status - # Use a psa test to assess whether the person has prostate cancer: - dx_result = hs.dx_manager.run_dx_test( - dx_tests_to_run='biopsy_for_prostate_cancer', - hsi_event=self - ) 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']) - if dx_result and cons_available: - # record date of diagnosis: - df.at[person_id, 'pc_date_diagnosis'] = self.sim.date + if cons_available: + # If consumables are available update the use of equipment and run the dx_test representing the biopsy + self.EQUIPMENT.update({'Ultrasound scanning machine', 'Ordinary Microscope'}) - # Check if is in metastatic stage: - in_metastatic = df.at[person_id, 'pc_status'] == 'metastatic' - # If the diagnosis does detect cancer, it is assumed that the classification as metastatic is made - # accurately. + # 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', + hsi_event=self + ) - if not in_metastatic: - # start treatment: - hs.schedule_hsi_event( - hsi_event=HSI_ProstateCancer_StartTreatment( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) - else: - # start palliative care: - hs.schedule_hsi_event( - hsi_event=HSI_ProstateCancer_PalliativeCare( - module=self.module, - person_id=person_id - ), - priority=0, - topen=self.sim.date, - tclose=None - ) + if dx_result: + # record date of diagnosis: + df.at[person_id, 'pc_date_diagnosis'] = self.sim.date + + # Check if is in metastatic stage: + in_metastatic = df.at[person_id, 'pc_status'] == 'metastatic' + # If the diagnosis does detect cancer, it is assumed that the classification as metastatic is made + # accurately. + + if not in_metastatic: + # start treatment: + hs.schedule_hsi_event( + hsi_event=HSI_ProstateCancer_StartTreatment( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) + else: + # start palliative care: + hs.schedule_hsi_event( + hsi_event=HSI_ProstateCancer_PalliativeCare( + module=self.module, + person_id=person_id + ), + priority=0, + topen=self.sim.date, + tclose=None + ) class HSI_ProstateCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): @@ -853,6 +861,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) + self.EQUIPMENT = set() # equipment as required for surgery @@ -889,6 +898,11 @@ def apply(self, person_id, squeeze_factor): 'treatment_surgery_optional']) if cons_available: + + # If consumables are available and the treatment will go ahead - update the equipment + self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', + 'Blood pressure machine', 'Pulse oximeter'}) + # Record date and stage of starting treatment df.at[person_id, "pc_date_treatment"] = self.sim.date df.at[person_id, "pc_stage_at_which_treatment_given"] = df.at[person_id, "pc_status"] @@ -919,6 +933,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' + self.EQUIPMENT = set() # possibly biopsy and histology @@ -977,6 +992,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) + self.EQUIPMENT = set() # generally not sure equipment is required as therapy is with drug, but can require castration surgery @@ -995,6 +1011,9 @@ 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 - update the equipment + self.EQUIPMENT.update({'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"]): df.at[person_id, "pc_date_palliative_care"] = self.sim.date From a057c40f87e7bc6371e716a8e73c0ec7db2e8f69 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 12 Jan 2024 15:01:19 +0000 Subject: [PATCH 234/443] formatting --- src/tlo/methods/cancer_consumables.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/tlo/methods/cancer_consumables.py b/src/tlo/methods/cancer_consumables.py index 1a44afd82c..685f03db4d 100644 --- a/src/tlo/methods/cancer_consumables.py +++ b/src/tlo/methods/cancer_consumables.py @@ -2,11 +2,6 @@ This file stores defines the consumables required within the cancer modules """ -import numpy as np -import pandas as pd - -from tlo import logging - def get_consumable_item_codes_cancers(self, cons_dict): """ From bfaef67dd2dd7b689ad20f873e952ae0e9e762e6 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 12 Jan 2024 15:03:16 +0000 Subject: [PATCH 235/443] adding equipment declarations to depression - none needed --- src/tlo/methods/depression.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tlo/methods/depression.py b/src/tlo/methods/depression.py index dbbb90db21..5d4e22ff1c 100644 --- a/src/tlo/methods/depression.py +++ b/src/tlo/methods/depression.py @@ -786,8 +786,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MentOPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.num_of_sessions_had = 0 # A counter for the number of sessions of talking therapy had - - # no equipment needed + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): """Set the property `de_ever_talk_ther` to be True and schedule the next session in the course if the person @@ -820,8 +819,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Depression_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MentOPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - - # no equipment needed + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -863,8 +861,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Depression_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - - # no equipment needed + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 7b4e331ebc80b8a3ee7e09b6674841c6a4f7180a Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 12 Jan 2024 15:06:05 +0000 Subject: [PATCH 236/443] adding equipment declarations to epilepsy - none needed --- src/tlo/methods/epilepsy.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/epilepsy.py b/src/tlo/methods/epilepsy.py index 067e53a006..0ccf714d49 100644 --- a/src/tlo/methods/epilepsy.py +++ b/src/tlo/methods/epilepsy.py @@ -590,8 +590,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Epilepsy_Treatment_Start' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - - # no equipment needed + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -640,8 +639,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self._DEFAULT_APPT_FOOTPRINT self.ACCEPTED_FACILITY_LEVEL = '1b' self._counter_of_failed_attempts_due_to_unavailable_medicines = 0 - - # no equipment needed + self.EQUIPMENT = set() def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 7cb9f2afeee5b052987e558c028a94eb13d1b711 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 12 Jan 2024 15:08:10 +0000 Subject: [PATCH 237/443] formatting --- src/tlo/methods/bladder_cancer.py | 2 +- src/tlo/methods/breast_cancer.py | 2 +- src/tlo/methods/oesophagealcancer.py | 2 +- src/tlo/methods/other_adult_cancers.py | 2 +- src/tlo/methods/prostate_cancer.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 782e4f83f3..9514c7800b 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -13,7 +13,7 @@ from tlo import DateOffset, Module, Parameter, Property, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import cancer_consumables, Metadata +from tlo.methods import Metadata, cancer_consumables from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1c461a5b6b..7569d34e72 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -12,7 +12,7 @@ from tlo import DateOffset, Module, Parameter, Property, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import cancer_consumables, Metadata +from tlo.methods import Metadata, cancer_consumables from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index 0c2250a388..85c7d99747 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -14,7 +14,7 @@ from tlo import DateOffset, Module, Parameter, Property, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import cancer_consumables, Metadata +from tlo.methods import Metadata, cancer_consumables from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 0b6ed4ad0b..95c708c98d 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -12,7 +12,7 @@ from tlo import DateOffset, Module, Parameter, Property, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import cancer_consumables, Metadata +from tlo.methods import Metadata, cancer_consumables from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index ea831b35ac..2f775286ac 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -12,7 +12,7 @@ from tlo import DateOffset, Module, Parameter, Property, Types, logging from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import cancer_consumables, Metadata +from tlo.methods import Metadata, cancer_consumables from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest From 9b173d76174c1ba0854d9a32d1efc05e18ff1af4 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 12 Jan 2024 15:09:41 +0000 Subject: [PATCH 238/443] fix error in cancer_consumables.py --- src/tlo/methods/cancer_consumables.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/cancer_consumables.py b/src/tlo/methods/cancer_consumables.py index 685f03db4d..36b07f1049 100644 --- a/src/tlo/methods/cancer_consumables.py +++ b/src/tlo/methods/cancer_consumables.py @@ -64,7 +64,7 @@ def get_list_of_items(item_list): get_list_of_items(['Blood collecting tube, 5 ml' 'Disposables gloves, powder free, 100 pieces per box']) - elif self == self.sim.modules['Bladder_cancer']: + elif self == self.sim.modules['BladderCancer']: # Note: bladder cancer is not in the malawi STG 2023 therefore no details on chemotherapy cons_dict['screening_cystoscopy_core'] = get_list_of_items(['Cytoscope']) From c53dc8c27c003466cfd5223cc0a9afbb2e536b86 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 12 Jan 2024 15:46:58 +0000 Subject: [PATCH 239/443] fix error in cancer_consumables.py --- src/tlo/methods/bladder_cancer.py | 3 ++- src/tlo/methods/cancer_consumables.py | 14 ++++++++++---- src/tlo/methods/oesophagealcancer.py | 5 +++-- src/tlo/methods/prostate_cancer.py | 5 ++--- 4 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 9514c7800b..8b0b718d8a 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -681,7 +681,8 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check consumables are available - cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_cystoscopy_core'], + # 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_breast_can['screening_biopsy_optional']) diff --git a/src/tlo/methods/cancer_consumables.py b/src/tlo/methods/cancer_consumables.py index 36b07f1049..96e23bda6a 100644 --- a/src/tlo/methods/cancer_consumables.py +++ b/src/tlo/methods/cancer_consumables.py @@ -58,22 +58,28 @@ def get_list_of_items(item_list): elif self == self.sim.modules['ProstateCancer']: - cons_dict['screening_psa_test_core'] = get_list_of_items(['Prostate specific antigen test']) + # TODO: Prostate specific antigen test is listed in ResourceFile_Consumables_availability_and_usage but not + # ResourceFile_Consumables_Items_and_Package + # cons_dict['screening_psa_test_core'] = get_list_of_items(['Prostate specific antigen test']) cons_dict['screening_psa_test_optional'] = \ - get_list_of_items(['Blood collecting tube, 5 ml' + get_list_of_items(['Blood collecting tube, 5 ml', 'Disposables gloves, powder free, 100 pieces per box']) elif self == self.sim.modules['BladderCancer']: # Note: bladder cancer is not in the malawi STG 2023 therefore no details on chemotherapy - cons_dict['screening_cystoscopy_core'] = get_list_of_items(['Cytoscope']) + # TODO: cytoscope is listed in ResourceFile_Consumables_availability_and_usage but not + # ResourceFile_Consumables_Items_and_Packages + # cons_dict['screening_cystoscopy_core'] = get_list_of_items(['Cytoscope']) cons_dict['screening_cystoscope_optional'] = get_list_of_items(['Specimen container']) elif self == self.sim.modules['OesophagealCancer']: - cons_dict['screening_endoscope_core'] = get_list_of_items(['Endoscope']) + # TODO: endoscope is listed in ResourceFile_Consumables_availability_and_usage but not + # ResourceFile_Consumables_Items_and_Packages + # cons_dict['screening_endoscope_core'] = get_list_of_items(['Endoscope']) cons_dict['screening_endoscope_optional'] =\ get_list_of_items(['Specimen container', diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index 85c7d99747..b27acce836 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -668,9 +668,10 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check the consumables are available - cons_avail = self.get_consumables(item_codes=self.module.item_codes_oesophageal_can['screening_endoscope_core'], + # todo: replace with endoscope + cons_avail = self.get_consumables(item_codes=self.module.item_codes_oesophageal_can['screening_biopsy_core'], optional_item_codes= - self.module.item_codes_oesophageal_can['screening_endoscope_optional']) + self.module.item_codes_oesophageal_can['screening_biopsy_optional']) if cons_avail: # If consumables are available update equipment and run the dx_test representing the biopsy diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 2f775286ac..c7ca7750d7 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -703,9 +703,8 @@ def apply(self, person_id, squeeze_factor): ) # Check consumable availability - cons_avail = self.get_consumables(item_codes=self.module.item_codes_prostate_can['screening_psa_core'], - optional_item_codes=self.module.item_codes_prostate_can[ - 'screening_psa_optional']) + # 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: # send for biopsy From ab105e8c56630320702d3e65556bf714dbfd36ad Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 12 Jan 2024 16:17:08 +0000 Subject: [PATCH 240/443] fix error in cancer_consumables.py --- src/tlo/methods/cancer_consumables.py | 8 ++++---- src/tlo/methods/prostate_cancer.py | 6 ++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/tlo/methods/cancer_consumables.py b/src/tlo/methods/cancer_consumables.py index 96e23bda6a..0abe4ed04a 100644 --- a/src/tlo/methods/cancer_consumables.py +++ b/src/tlo/methods/cancer_consumables.py @@ -47,7 +47,7 @@ def get_list_of_items(item_list): 'Disposables gloves, powder free, 100 pieces per box' ]) - if self == self.sim.modules['BreastCancer']: + if ('BreastCancer' in self.sim.modules) and (self == self.sim.modules['BreastCancer']): # TODO: chemotharpy protocols??: TAC(Taxotere, Adriamycin, and Cyclophosphamide), AC (anthracycline and # cyclophosphamide) +/-Taxane, TC (Taxotere and cyclophosphamide), CMF (cyclophosphamide, methotrexate, @@ -56,7 +56,7 @@ def get_list_of_items(item_list): # only chemotherapy i consumable list which is also in suggested protocol is cyclo cons_dict['treatment_chemotherapy'] = get_list_of_items(['Cyclophosphamide, 1 g']) - elif self == self.sim.modules['ProstateCancer']: + elif ('ProstateCancer' in self.sim.modules) and (self == self.sim.modules['ProstateCancer']): # TODO: Prostate specific antigen test is listed in ResourceFile_Consumables_availability_and_usage but not # ResourceFile_Consumables_Items_and_Package @@ -66,7 +66,7 @@ def get_list_of_items(item_list): get_list_of_items(['Blood collecting tube, 5 ml', 'Disposables gloves, powder free, 100 pieces per box']) - elif self == self.sim.modules['BladderCancer']: + elif ('BladderCancer' in self.sim.modules) and (self == self.sim.modules['BladderCancer']): # Note: bladder cancer is not in the malawi STG 2023 therefore no details on chemotherapy # TODO: cytoscope is listed in ResourceFile_Consumables_availability_and_usage but not @@ -75,7 +75,7 @@ def get_list_of_items(item_list): cons_dict['screening_cystoscope_optional'] = get_list_of_items(['Specimen container']) - elif self == self.sim.modules['OesophagealCancer']: + elif ('OesophagealCancer' in self.sim.modules) and (self == self.sim.modules['OesophagealCancer']): # TODO: endoscope is listed in ResourceFile_Consumables_availability_and_usage but not # ResourceFile_Consumables_Items_and_Packages diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index c7ca7750d7..311ea444d4 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -754,10 +754,8 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - # Check consumable availability - cons_avail = self.get_consumables(item_codes=self.module.item_codes_prostate_can['screening_psa_core'], - optional_item_codes=self.module.item_codes_prostate_can[ - 'screening_psa_optional']) + # 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: # send for biopsy From 451f262adfd3fc40b7d906a87e01bcd557a918ce Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 12:11:31 +0100 Subject: [PATCH 241/443] TODO: some modules use equipment when talking about consumables --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 ++ src/tlo/methods/labour.py | 2 ++ src/tlo/methods/newborn_outcomes.py | 2 ++ src/tlo/methods/rti.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 0744bbde79..b710de5ca1 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -216,6 +216,8 @@ def get_and_store_pregnancy_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_preg_consumables['blood_test_equipment'] = \ get_list_of_items(self, ['Disposables gloves, powder free, 100 pieces per box']) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 92d6a446dd..d9b6118596 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -692,6 +692,8 @@ def get_and_store_labour_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_lab_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 36104cf2ee..1905ac1d9f 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -410,6 +410,8 @@ def get_and_store_newborn_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_nb_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index aac0129cf6..0d788aaf6c 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4665,6 +4665,8 @@ def apply(self, person_id, squeeze_factor): # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, # equipment used by surgeon, gloves and facemask + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From b8da67364fc5433ddab26f089099da91460a4401 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:11:12 +0100 Subject: [PATCH 242/443] breast_cancer: dummy used_equipment added where Andrew requested diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 1ce9ad2bf..56c935fba 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -666,6 +666,8 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -759,6 +761,9 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Record used equipment + self.used_equipment = 'Anything used for mastectomy as I guess this is about' + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( --- src/tlo/methods/breast_cancer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1ce9ad2bf6..56c935fba2 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -666,6 +666,8 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -759,6 +761,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Record used equipment + self.used_equipment = 'Anything used for mastectomy as I guess this is about' + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( From d685c69bdd3b974f9b40e1b64be43f546b2d77a5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:15:40 +0100 Subject: [PATCH 243/443] co: dummy used_equipment added for methods where Emi listed some --- src/tlo/methods/contraception.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 4238ae04d2..6ffb0ebc6e 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1255,7 +1255,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 record 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: @@ -1279,6 +1280,13 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Record used equipment when needed + if _new_contraceptive == 'female_sterilization': + self.used_equipment = {'Smt used to sterilize a woman'} + elif _new_contraceptive == 'IUD': + self.used_equipment = {'Equipment used when performing IUD'} + else: _new_contraceptive = "not_using" From a00d25a5e324d648fbd195acf1b0ff472ac65149 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:24:02 +0100 Subject: [PATCH 244/443] healthsystem: annual equipment summary log by fac. level --- src/tlo/methods/healthsystem.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index b6a509f1f4..75db01aa27 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,6 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + self.used_equipment = set() @property def bed_days_allocated_to_this_event(self): @@ -1740,6 +1741,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, + equipment=hsi_event.used_equipment, ) def write_to_hsi_log( @@ -1750,6 +1752,7 @@ def write_to_hsi_log( squeeze_factor: float, did_run: bool, priority: int, + equipment: set, ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1779,6 +1782,7 @@ def write_to_hsi_log( squeeze_factor=squeeze_factor, appt_footprint=event_details.appt_footprint, level=event_details.facility_level, + equipment=equipment, ) def call_and_record_never_ran_hsi_event(self, hsi_event, priority=None): @@ -2601,6 +2605,7 @@ def apply(self, population): squeeze_factor=0.0, priority=-1, did_run=True, + equipment=set() # TODO: explore more, should it be non-emtpy in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2655,6 +2660,7 @@ def _reset_internal_stores(self) -> None: self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} # <--Same as `self._appts` but also split by facility_level + self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran @@ -2671,7 +2677,8 @@ def record_hsi_event(self, hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str + level: str, + equipment: set ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2688,6 +2695,9 @@ def record_hsi_event(self, self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number + # Update used equipment by level + self._equip_by_level[level].update(equipment) + def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2750,6 +2760,14 @@ def write_to_log_and_reset_counters(self): }, ) + logger_summary.info( + key="Equipment", + description="Sets of used equipment for each facility level in this calendar year.", + data={ + "Equipment_By_Level": self._equip_by_level, + }, + ) + self._reset_internal_stores() From b7cc8528cf12fa666dde3660e216dad6a1169287 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 16:15:18 +0100 Subject: [PATCH 245/443] breast_cancer: mastectomy dummy equipment fixed --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 56c935fba2..5d8fabfcb2 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -762,7 +762,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] # Record used equipment - self.used_equipment = 'Anything used for mastectomy as I guess this is about' + self.used_equipment = {'Anything used for mastectomy as I guess this is about'} # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( From 7f5c5754429fd5475ced34ea4204d8048b3ca7d3 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Sep 2023 21:01:35 +0100 Subject: [PATCH 246/443] equipment_catalogue & utils: new script + a change in utils.py - to create equip. catalogue --- .../equipment/equipment_catalogue.py | 101 ++++++++++++++++++ src/tlo/analysis/utils.py | 8 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/scripts/healthsystem/equipment/equipment_catalogue.py diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py new file mode 100644 index 0000000000..c88a469c2f --- /dev/null +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -0,0 +1,101 @@ +import argparse +import pandas as pd +from pathlib import Path +from tlo.analysis.utils import extract_results +from functools import reduce + + +def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: + """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated + year.""" + + def get_equipment_declaration_by_levels(_df): + """Get the equipment declaration by facility levels for the year.""" + + def unpack_dict_in_series(_raw: pd.Series): + # Create an empty DataFrame to store the data + df = pd.DataFrame() + + # Iterate through the dictionary items + for col_name, mydict in _raw.items(): + for date, inner_dict in mydict.items(): + # Convert the inner_dict to a list of dictionaries with 'date' + data = [{'date': date, 'fac_level': inner_dict_key, 'value': inner_dict_set} for + inner_dict_key, inner_dict_set in inner_dict.items()] + # Create a DataFrame from the list with date & fac_level as indexes + temp_df = pd.DataFrame(data) + temp_df.set_index(['date', 'fac_level'], inplace=True) + temp_df.columns = [None] + + # Concatenate the temporary DataFrame to the result DataFrame + df = pd.concat([df, temp_df]) + + # print(f"\ndf\n {df}") + df.columns = [None] + + return df + + return _df \ + .set_index('date') \ + .pipe(unpack_dict_in_series) \ + .stack() \ + .droplevel(level=2) + + return extract_results( + results_folder, + module='tlo.methods.healthsystem.summary', + key='Equipment', + custom_generate_series=get_equipment_declaration_by_levels + ) + + +def create_equipment_catalogue(results_folder: Path, output_folder: Path): + # Declare path for output file from this script + output_file_name = 'equipment_catalogue_by_level.csv' + output_detailed_file_name = 'equipment_catalogue_by_date_level_sim.csv' + + sim_equipment = get_annual_equipment_declarations_by_levels(results_folder) + sim_equipment_df = pd.DataFrame(sim_equipment) + sim_equipment_df.index.names = ['date', 'fac_level'] + + # Save the detailed CSV + sim_equipment_df.to_csv(output_folder / output_detailed_file_name) + print('equipment_catalogue_by_date_level_sim.csv saved.') + + # Prepare a catalogue only by facility levels + # Define a custom aggregation function to combine sets in columns for each row + def combine_sets(row): + combined_set = set() + for col in row: + combined_set.update(col) + return combined_set + + # Apply the custom aggregation function to each row + sim_equipment_by_level_df = sim_equipment_df.copy() + sim_equipment_by_level_df['equipment'] = sim_equipment_by_level_df.apply(combine_sets, axis=1) + # Group by 'fac_level' and join rows with the same 'fac_level' into one set + sim_equipment_by_level_df.reset_index(inplace=True) + sim_equipment_by_level_df = sim_equipment_by_level_df.groupby('fac_level')['equipment'].apply( + lambda x: list(set.union(*x)) + ).reset_index() + + # Explode the 'equipment' column to separate elements into rows + sim_equipment_by_level_df = sim_equipment_by_level_df.explode('equipment', ignore_index=True).set_index('fac_level') + + # Save the CSV equipment catalogue + sim_equipment_by_level_df.to_csv(output_folder / output_file_name) + print('equipment_catalogue_by_level.csv saved.') + + return 0 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("results_folder", type=Path) + args = parser.parse_args() + + create_equipment_catalogue( + results_folder=args.results_folder, + output_folder=args.results_folder, + ) +# NB. Edit run configuration, the Parameters: "./outputs/sejjej5@ucl.ac.uk/long_run_all_diseases-2023-09-04T233551Z" diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index 52c39e617c..e2d5db608d 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -282,6 +282,9 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series: # get number of draws and numbers of runs info = get_scenario_info(results_folder) + def is_number(element): + return isinstance(element, (int, float)) + # Collect results from each draw/run res = dict() for draw in range(info['number_of_draws']): @@ -293,7 +296,10 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series: df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key] output_from_eval: pd.Series = generate_series(df) assert pd.Series == type(output_from_eval), 'Custom command does not generate a pd.Series' - res[draw_run] = output_from_eval * get_multiplier(draw, run) + if output_from_eval.apply(is_number).all(): + res[draw_run] = output_from_eval * get_multiplier(draw, run) + else: + res[draw_run] = output_from_eval except KeyError: # Some logs could not be found - probably because this run failed. From c4b316ba8344176062e25e9c7ba4f7a997281ace Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 7 Sep 2023 17:12:45 +0100 Subject: [PATCH 247/443] equipment_catalogue: PEP8 --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index c88a469c2f..fdaca54515 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -1,8 +1,9 @@ import argparse -import pandas as pd from pathlib import Path + +import pandas as pd + from tlo.analysis.utils import extract_results -from functools import reduce def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: From 79ba1c1a77d8b3b55757a08fc05722b35e1535e2 Mon Sep 17 00:00:00 2001 From: Eva Janouskova <48157464+EvaJanouskova@users.noreply.github.com> Date: Tue, 12 Sep 2023 19:53:10 +0200 Subject: [PATCH 248/443] healthsystem: sort equipment for log Co-authored-by: Tim Hallett <39991060+tbhallett@users.noreply.github.com> --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 75db01aa27..c19b0f4337 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2764,7 +2764,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": self._equip_by_level, + "Equipment_By_Level": sorted(self._equip_by_level), }, ) From 328bb7868f1d1ca33a2ebe7073bfe1b1240bb619 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 18:26:35 +0100 Subject: [PATCH 249/443] equipment_catalogue: comment updated --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index fdaca54515..ecbc206aba 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -8,7 +8,8 @@ def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated - year.""" + year. + NB. healthsystem.summary logger required to have been set at the level INFO or higher.""" def get_equipment_declaration_by_levels(_df): """Get the equipment declaration by facility levels for the year.""" From 8fae9b37123243647229ef05d9e867fd4431777c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:10:01 +0100 Subject: [PATCH 250/443] rti: unified use of consumables/equipment terms --- src/tlo/methods/rti.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 0d788aaf6c..f92d751e14 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4664,9 +4664,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe @@ -5002,7 +5000,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From c4615dacd1abf00d4879765df7731e5c40ade4cb Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:15:44 +0100 Subject: [PATCH 251/443] hs, brc, co: used_equipment renamed to EQUIPMENT; if equip always same for HSI - set in __init__, otherwise updated in apply diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 5d8fabfcb..26155729a 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -646,6 +646,8 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -666,8 +668,6 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -761,8 +761,9 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Record used equipment - self.used_equipment = {'Anything used for mastectomy as I guess this is about'} + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 6ffb0ebc6..15851e1b7 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1281,11 +1281,12 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) _new_contraceptive = self.new_contraceptive - # Record used equipment when needed + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': - self.used_equipment = {'Smt used to sterilize a woman'} + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': - self.used_equipment = {'Equipment used when performing IUD'} + self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: _new_contraceptive = "not_using" diff --git src/tlo/methods/healthsystem.py src/tlo/methods/healthsystem.py index c19b0f433..3eb6b9940 100644 --- src/tlo/methods/healthsystem.py +++ src/tlo/methods/healthsystem.py @@ -182,7 +182,7 @@ class HSI_Event: self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - self.used_equipment = set() + self.EQUIPMENT = set() @property def bed_days_allocated_to_this_event(self): @@ -1741,7 +1741,7 @@ class HealthSystem(Module): squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.used_equipment, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( --- src/tlo/methods/breast_cancer.py | 9 +++++---- src/tlo/methods/contraception.py | 7 ++++--- src/tlo/methods/healthsystem.py | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 5d8fabfcb2..26155729ab 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,6 +646,8 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -666,8 +668,6 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -761,8 +761,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Record used equipment - self.used_equipment = {'Anything used for mastectomy as I guess this is about'} + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 6ffb0ebc6e..15851e1b77 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1281,11 +1281,12 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Record used equipment when needed + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': - self.used_equipment = {'Smt used to sterilize a woman'} + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': - self.used_equipment = {'Equipment used when performing IUD'} + self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: _new_contraceptive = "not_using" diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index c19b0f4337..3eb6b99400 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,7 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - self.used_equipment = set() + self.EQUIPMENT = set() @property def bed_days_allocated_to_this_event(self): @@ -1741,7 +1741,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.used_equipment, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( From 0d9e216b8cf3e5eb26e53f756c72e4f4da647584 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 18 Sep 2023 17:33:16 +0100 Subject: [PATCH 252/443] brc: comment updated diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 26155729a..aef476c87 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -647,7 +647,7 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 26155729ab..aef476c870 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -647,7 +647,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props From ecc5981725f687d9646695e250b24779e7f692b7 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:44:03 +0100 Subject: [PATCH 253/443] brc & co: rm the dummy examples of equipment from modules --- src/tlo/methods/breast_cancer.py | 6 ------ src/tlo/methods/contraception.py | 10 +--------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index aef476c870..1ce9ad2bf6 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,8 +646,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -761,10 +759,6 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Update equipment used with treatment - # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher - self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) - # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 15851e1b77..f86e342c83 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1255,8 +1255,7 @@ 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 and record used equipment if any, if not set the contraception to - # "not_using": + # if so do log the availability of all items, 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: @@ -1281,13 +1280,6 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Update equipment when needed - # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher - if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) - elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Equipment used when performing IUD'}) - else: _new_contraceptive = "not_using" From a3d726e5fbec32341f29ea1c64da793e276a7b2c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 24 Sep 2023 16:15:42 +0100 Subject: [PATCH 254/443] RF_Equipment: equipment catalogue - first draft (from Sakshi) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv new file mode 100644 index 0000000000..b119a1d503 --- /dev/null +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17a3a35a9c7908c9a291b8906ae1f8f7be6ff89459a183900de142ee29368c53 +size 36731 From d8a53b2ec1c31137bb16f7c15a16884e14b98617 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 24 Sep 2023 23:54:02 +0100 Subject: [PATCH 255/443] RF_Equipment: equipment catalogue - merge duplicates (round 1) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index b119a1d503..4e936f4cb9 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17a3a35a9c7908c9a291b8906ae1f8f7be6ff89459a183900de142ee29368c53 -size 36731 +oid sha256:faf0337415b4ef87fc47dace921c9f9fb4450d88ff1718def0a96e80fa073cb6 +size 34520 From e7a8e56529ff5f6b8e0ffd57a247751a8baf6bae Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 09:56:16 +0100 Subject: [PATCH 256/443] RF_Equipment: equipment catalogue - merge duplicates (round 2) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 4e936f4cb9..22f62d5a0f 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:faf0337415b4ef87fc47dace921c9f9fb4450d88ff1718def0a96e80fa073cb6 -size 34520 +oid sha256:b4f78c47032a3e658fc1d07fd631c2e8aea85b145a7978fcd5d965bf9e0c5fce +size 32543 From bd55b0d29188fb0b51bd0b5489676d6fe70ba75e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 21:50:20 +0100 Subject: [PATCH 257/443] hs: debugging Equipment log - rm sorted --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 3eb6b99400..1532602c92 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2764,7 +2764,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": sorted(self._equip_by_level), + "Equipment_By_Level": self._equip_by_level, }, ) From ed50338cbb18a74134b21542f900f00ce09413c8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 28 Sep 2023 00:49:34 +0100 Subject: [PATCH 258/443] hs: fix sorting of _equip_by_level --- src/tlo/methods/healthsystem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1532602c92..fc69993e8d 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2760,6 +2760,9 @@ def write_to_log_and_reset_counters(self): }, ) + # Sort equipment within levels, and log them + for key in self._equip_by_level: + self._equip_by_level[key] = sorted(self._equip_by_level[key]) logger_summary.info( key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", From 5576de9058486f0bce021ba612ca659b60914a51 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 19:08:35 +0100 Subject: [PATCH 259/443] RF_Equipment: equipment catalogue - merge duplicates (round 3) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 22f62d5a0f..6178c242ba 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4f78c47032a3e658fc1d07fd631c2e8aea85b145a7978fcd5d965bf9e0c5fce -size 32543 +oid sha256:9f02f434986e8070dacc50405e2b88826be18eb6de61b4e6475e7a4cad948334 +size 32455 From ba970988bec11e4cc7abfc9e43cea4b8e3ba4f40 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 28 Sep 2023 00:32:23 +0100 Subject: [PATCH 260/443] RF_Equipment: equipment catalogue - merge duplicates (round 4) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 6178c242ba..aca3040b03 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f02f434986e8070dacc50405e2b88826be18eb6de61b4e6475e7a4cad948334 -size 32455 +oid sha256:29782fdacd7b13efef9b0b9de23fb7413b329bb9880b87c3b0522e6732130bc1 +size 31872 From d114b2d06561942f4ef6e1b7a7b63be570590a98 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 29 Sep 2023 10:45:25 +0100 Subject: [PATCH 261/443] RF_Equipment: equipment catalogue - merge duplicates (round 5) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index aca3040b03..0055cd8d8e 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29782fdacd7b13efef9b0b9de23fb7413b329bb9880b87c3b0522e6732130bc1 -size 31872 +oid sha256:f3854096278164e1e3c63c2aa2e7afecbae1c6ee14dabcff829af78c271cd82d +size 31712 From e88babe8a0dea070e419a8257c789b075746f335 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:09:49 +0000 Subject: [PATCH 262/443] RF_Equipment: equipment catalogue - merge duplicates (round 6) + col names renamed --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 0055cd8d8e..c7f2e7e240 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3854096278164e1e3c63c2aa2e7afecbae1c6ee14dabcff829af78c271cd82d -size 31712 +oid sha256:c1f647adbbd18cbc1460135450d2f0b402fac00f80f050fcb3cd8c138b822f71 +size 31663 From 13af328d16bec5155d7e6aee3cb9ecf71fcdbddf Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:23:41 +0000 Subject: [PATCH 263/443] RF_Equip: equip item codes added --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index c7f2e7e240..f0a2b53c9f 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1f647adbbd18cbc1460135450d2f0b402fac00f80f050fcb3cd8c138b822f71 -size 31663 +oid sha256:cd2c3558a4d30dfb6d913054513e1d6df1b91f4955b60371e762eff05ac99afd +size 32759 From ac22da6fa6f4cd3dbe1fbe0cb8c7ea96d820269e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:21:55 +0000 Subject: [PATCH 264/443] codes_to_items_list: new script created --- src/tlo/analysis/codes_to_items_list.py | 70 +++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/tlo/analysis/codes_to_items_list.py diff --git a/src/tlo/analysis/codes_to_items_list.py b/src/tlo/analysis/codes_to_items_list.py new file mode 100644 index 0000000000..365f2e64b8 --- /dev/null +++ b/src/tlo/analysis/codes_to_items_list.py @@ -0,0 +1,70 @@ +""" +(1) Can be used for a list of items without item codes yet saved in a csv file named 'csv_file_to_update_name'. + +This script will assign unique code to each unique item name which has no code assigned yet. The codes are +assigned in order from the sequence 0, 1, 2, .... + +Duplicated items are allowed, the same code will be assigned to the same items. + +(2) Can be used when new items are added later without item codes but some items with codes are already in the list. + +This script will keep the existing codes for items with already assigned code and for items without existing +code will assign new code (continue in sequence, i.e. if the highest code is 5, it assigns new codes from the continuing +sequence 6, 7, 8, ...). + +------ +NB. Make sure the 'csv_file_to_update_name' is the file you want to update. The output will be named +'csv_file_to_update_name' + '_new.csv' to avoid unintentionally losing the previous version. +------ +""" + +import pandas as pd +from pathlib import Path + + +# ## CHANGE THIS IF YOU WANT TO USE DIFFERENT FILE AS INPUT +csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' + +# Get the path of the current script file +script_path = Path(__file__) +print(script_path) + +# Specify the file path to RF csv file +file_path = script_path.parent.parent.parent.parent / 'resources/healthsystem/infrastructure_and_equipment' + +# Load the CSV RF into a DataFrame +df = pd.read_csv(Path(file_path) / str(csv_file_to_update_name + '.csv')) + +# Find unique values in Equipment that have no code and are not None or empty +unique_values =\ + df.loc[df['Equip_Code'].isna() & df['Equip_Item'].notna() & (df['Equip_Item'] != ''), 'Equip_Item'].unique() + +# Create a mapping of unique values to codes +value_to_code = {} +# Initialize the starting code value +if not df['Equip_Code'].isna().all(): + next_code = int(df['Equip_Code'].max()) + 1 +else: + next_code = 0 + +# Iterate through unique values +for value in unique_values: + # Check if there is at least one existing code for this value + matching_rows = df.loc[df['Equip_Item'] == value, 'Equip_Code'].dropna() + if not matching_rows.empty: + # Use the existing code for this value + existing_code = int(matching_rows.iloc[0]) + # TODO: verify all the codes are the same + else: + # If no existing codes, start with the next available code + existing_code = next_code + next_code += 1 + value_to_code[value] = existing_code + # Update the 'Equip_Code' column for matching rows + df.loc[df['Equip_Item'] == value, 'Equip_Code'] = existing_code + +# Convert 'Equip_Code' column to integers +df['Equip_Code'] = df['Equip_Code'].astype('Int64') # Convert to nullable integer type + +# Save CSV with equipment codes +df.to_csv(Path(file_path) / str(csv_file_to_update_name + '_new.csv'), index=False) From 178e996e8acaa79dcf66c59d884c8b1df6781851 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:45:42 +0000 Subject: [PATCH 265/443] codes_to_items_list: script generalised + PEP 8 --- src/tlo/analysis/codes_to_items_list.py | 31 ++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/tlo/analysis/codes_to_items_list.py b/src/tlo/analysis/codes_to_items_list.py index 365f2e64b8..da169a06e5 100644 --- a/src/tlo/analysis/codes_to_items_list.py +++ b/src/tlo/analysis/codes_to_items_list.py @@ -18,39 +18,44 @@ ------ """ -import pandas as pd from pathlib import Path - -# ## CHANGE THIS IF YOU WANT TO USE DIFFERENT FILE AS INPUT -csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' +import pandas as pd # Get the path of the current script file script_path = Path(__file__) print(script_path) -# Specify the file path to RF csv file +# ############################# +# ## CHANGE THIS FOR YOUR FILE +# Specify name of the csv file +csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' +# Specify the file path to csv file file_path = script_path.parent.parent.parent.parent / 'resources/healthsystem/infrastructure_and_equipment' +# Specify the names of columns containing the item names and item codes +item_col_name = 'Equip_Item' +code_col_name = 'Equip_Code' +# ############################# # Load the CSV RF into a DataFrame df = pd.read_csv(Path(file_path) / str(csv_file_to_update_name + '.csv')) # Find unique values in Equipment that have no code and are not None or empty unique_values =\ - df.loc[df['Equip_Code'].isna() & df['Equip_Item'].notna() & (df['Equip_Item'] != ''), 'Equip_Item'].unique() + df.loc[df[code_col_name].isna() & df[item_col_name].notna() & (df[item_col_name] != ''), item_col_name].unique() # Create a mapping of unique values to codes value_to_code = {} # Initialize the starting code value -if not df['Equip_Code'].isna().all(): - next_code = int(df['Equip_Code'].max()) + 1 +if not df[code_col_name].isna().all(): + next_code = int(df[code_col_name].max()) + 1 else: next_code = 0 # Iterate through unique values for value in unique_values: # Check if there is at least one existing code for this value - matching_rows = df.loc[df['Equip_Item'] == value, 'Equip_Code'].dropna() + matching_rows = df.loc[df[item_col_name] == value, code_col_name].dropna() if not matching_rows.empty: # Use the existing code for this value existing_code = int(matching_rows.iloc[0]) @@ -60,11 +65,11 @@ existing_code = next_code next_code += 1 value_to_code[value] = existing_code - # Update the 'Equip_Code' column for matching rows - df.loc[df['Equip_Item'] == value, 'Equip_Code'] = existing_code + # Update the code_col_name column for matching rows + df.loc[df[item_col_name] == value, code_col_name] = existing_code -# Convert 'Equip_Code' column to integers -df['Equip_Code'] = df['Equip_Code'].astype('Int64') # Convert to nullable integer type +# Convert code_col_name column to integers +df[code_col_name] = df[code_col_name].astype('Int64') # Convert to nullable integer type # Save CSV with equipment codes df.to_csv(Path(file_path) / str(csv_file_to_update_name + '_new.csv'), index=False) From b7c4e866703f2152d0c3693af25d1071070b066c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 16 Nov 2023 17:55:35 +0000 Subject: [PATCH 266/443] hs: equipment added to HSIEventDetails --- src/tlo/methods/healthsystem.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index fc69993e8d..5e3414d22c 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -126,6 +126,7 @@ class HSIEventDetails(NamedTuple): facility_level: Optional[str] appt_footprint: Tuple[Tuple[str, int]] beddays_footprint: Tuple[Tuple[str, int]] + equipment: set class HSIEventQueueItem(NamedTuple): @@ -399,7 +400,8 @@ def as_namedtuple( appt_footprint=tuple(sorted(appt_footprint.items())), beddays_footprint=tuple( sorted((k, v) for k, v in self.BEDDAYS_FOOTPRINT.items() if v > 0) - ) + ), + equipment=(tuple(self.EQUIPMENT)) ) @@ -1767,10 +1769,13 @@ def write_to_hsi_log( 'did_run': did_run, 'Facility_Level': event_details.facility_level if event_details.facility_level is not None else -99, 'Facility_ID': facility_id if facility_id is not None else -99, + 'equipment': equipment, }, description="record of each HSI event" ) if did_run: + print("\nevent_details") + print(event_details) if self._hsi_event_count_log_period is not None: event_details_key = self._hsi_event_details.setdefault( event_details, len(self._hsi_event_details) @@ -1815,7 +1820,7 @@ def write_to_never_ran_hsi_log( event_details: HSIEventDetails, person_id: int, facility_id: Optional[int], - priority: int, + priority: int ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1840,7 +1845,7 @@ def write_to_never_ran_hsi_log( treatment_id=event_details.treatment_id, hsi_event_name=event_details.event_name, appt_footprint=event_details.appt_footprint, - level=event_details.facility_level, + level=event_details.facility_level ) def log_current_capabilities_and_usage(self): @@ -2100,7 +2105,7 @@ def run_individual_level_events_in_mode_0_or_1(self, actual_appt_footprint=actual_appt_footprint, squeeze_factor=squeeze_factor, did_run=True, - priority=_priority + priority=_priority, ) # if not ok_to_run @@ -2598,7 +2603,8 @@ def apply(self, population): treatment_id='Inpatient_Care', facility_level=self.module._facility_by_facility_id[_fac_id].level, appt_footprint=tuple(sorted(_inpatient_appts.items())), - beddays_footprint=() + beddays_footprint=(), + equipment=() # TODO: what should be in here? ), person_id=-1, facility_id=_fac_id, From c9f9da7b01a54c21ed8f7a5a1e669ffd8225b447 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 17 Nov 2023 18:51:25 +0000 Subject: [PATCH 267/443] equip_catalogue: make catalogues from new logging (equip included in hsi_event_details, counts logged in hsi_event_counts) --- .../equipment/equipment_catalogue.py | 117 +++++++++++------- 1 file changed, 69 insertions(+), 48 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index ecbc206aba..9672c92474 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -3,36 +3,35 @@ import pandas as pd -from tlo.analysis.utils import extract_results +from tlo.analysis.utils import extract_results, load_pickled_dataframes -def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: - """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated - year. - NB. healthsystem.summary logger required to have been set at the level INFO or higher.""" +def get_annual_hsi_event_counts(results_folder: Path) -> pd.DataFrame: + """Return pd.DataFrame gives the simulated annual counts of all the hsi event details logged (details as keys) + for each simulated year. + NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" - def get_equipment_declaration_by_levels(_df): - """Get the equipment declaration by facility levels for the year.""" + def get_hsi_event_counts(_df): + """Get the counts of all the hsi event details logged.""" def unpack_dict_in_series(_raw: pd.Series): # Create an empty DataFrame to store the data df = pd.DataFrame() # Iterate through the dictionary items - for col_name, mydict in _raw.items(): + for _, mydict in _raw.items(): for date, inner_dict in mydict.items(): # Convert the inner_dict to a list of dictionaries with 'date' - data = [{'date': date, 'fac_level': inner_dict_key, 'value': inner_dict_set} for + data = [{'date': date, 'event_details_key': inner_dict_key, 'count': inner_dict_set} for inner_dict_key, inner_dict_set in inner_dict.items()] # Create a DataFrame from the list with date & fac_level as indexes temp_df = pd.DataFrame(data) - temp_df.set_index(['date', 'fac_level'], inplace=True) + temp_df.set_index(['date', 'event_details_key'], inplace=True) temp_df.columns = [None] # Concatenate the temporary DataFrame to the result DataFrame df = pd.concat([df, temp_df]) - # print(f"\ndf\n {df}") df.columns = [None] return df @@ -46,47 +45,69 @@ def unpack_dict_in_series(_raw: pd.Series): return extract_results( results_folder, module='tlo.methods.healthsystem.summary', - key='Equipment', - custom_generate_series=get_equipment_declaration_by_levels + key='hsi_event_counts', + custom_generate_series=get_hsi_event_counts ) -def create_equipment_catalogue(results_folder: Path, output_folder: Path): - # Declare path for output file from this script - output_file_name = 'equipment_catalogue_by_level.csv' - output_detailed_file_name = 'equipment_catalogue_by_date_level_sim.csv' +def get_hsi_event_keys(results_folder: Path): + return load_pickled_dataframes(results_folder, 0, 0, "tlo.methods.healthsystem.summary")[ + "tlo.methods.healthsystem.summary" + ]["hsi_event_details"]["hsi_event_key_to_event_details"][0] - sim_equipment = get_annual_equipment_declarations_by_levels(results_folder) + +def create_equipment_catalogues(results_folder: Path, output_folder: Path): + + # Declare output file names + output_detailed_file_name = 'equipment_annual_counts__all_event_details.csv' + output_file_name = 'equipment_annual_counts__by_Date_EventName_FacLevel.csv' + + # %% Catalogue equipment by all HSI event details + sim_equipment = get_annual_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) - sim_equipment_df.index.names = ['date', 'fac_level'] - - # Save the detailed CSV - sim_equipment_df.to_csv(output_folder / output_detailed_file_name) - print('equipment_catalogue_by_date_level_sim.csv saved.') - - # Prepare a catalogue only by facility levels - # Define a custom aggregation function to combine sets in columns for each row - def combine_sets(row): - combined_set = set() - for col in row: - combined_set.update(col) - return combined_set - - # Apply the custom aggregation function to each row - sim_equipment_by_level_df = sim_equipment_df.copy() - sim_equipment_by_level_df['equipment'] = sim_equipment_by_level_df.apply(combine_sets, axis=1) - # Group by 'fac_level' and join rows with the same 'fac_level' into one set - sim_equipment_by_level_df.reset_index(inplace=True) - sim_equipment_by_level_df = sim_equipment_by_level_df.groupby('fac_level')['equipment'].apply( - lambda x: list(set.union(*x)) - ).reset_index() - - # Explode the 'equipment' column to separate elements into rows - sim_equipment_by_level_df = sim_equipment_by_level_df.explode('equipment', ignore_index=True).set_index('fac_level') - - # Save the CSV equipment catalogue - sim_equipment_by_level_df.to_csv(output_folder / output_file_name) - print('equipment_catalogue_by_level.csv saved.') + sim_equipment_df.fillna(0, inplace=True) + + hsi_event_keys = get_hsi_event_keys(results_folder) + + decoded_keys = sim_equipment_df.index.get_level_values(1).astype(str).map(hsi_event_keys) + sim_equipment_df = pd.concat([sim_equipment_df, pd.DataFrame(decoded_keys.tolist(), index=sim_equipment_df.index)], axis=1) + # Make values in 'appt_footprint', and 'beddays_footprint' columns to be string + sim_equipment_df['appt_footprint'] = sim_equipment_df['appt_footprint'].apply(lambda x: ', '.join(map(str, x))) + sim_equipment_df['beddays_footprint'] = sim_equipment_df['beddays_footprint'].apply(lambda x: ', '.join(map(str, x))) + # Explode the 'equipment' column + exploded_df = sim_equipment_df.explode('equipment') + # Remove the 'event_details_key' and replace the index with hsi event details as indexes + exploded_df = exploded_df.droplevel(level=1) + exploded_df = exploded_df.set_index( + ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', + 'equipment'], append=True + ) + # Sum values with the same multi-index + exploded_df = exploded_df.groupby(exploded_df.index).sum() + # Convert the index back to a MultiIndex + exploded_df.index = pd.MultiIndex.from_tuples(exploded_df.index) + exploded_df.index.names = \ + ['date', 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', + 'equipment'] + + # Save the detailed equipment catalogue by levels + exploded_df.to_csv(output_folder / output_detailed_file_name) + print(f'{output_detailed_file_name} saved.') + # --- + + # %% Catalogue equipment by Facility Levels and HSI Event Names + + equipment_counts_by_date_hsi_name_level_df = exploded_df.copy() + + # Sum counts for each equipment with the same date, hsi event name, and level (remaining indexes removed) + # equipment_counts_by_date_hsi_name_level_df + equipment_counts_by_date_hsi_name_level_df = \ + equipment_counts_by_date_hsi_name_level_df.groupby(['date', 'event_name', 'facility_level', 'equipment']).sum() + + # Save the CSV equipment counts + equipment_counts_by_date_hsi_name_level_df.to_csv(output_folder / output_file_name) + print(f'{output_file_name} saved.') + # --- return 0 @@ -96,7 +117,7 @@ def combine_sets(row): parser.add_argument("results_folder", type=Path) args = parser.parse_args() - create_equipment_catalogue( + create_equipment_catalogues( results_folder=args.results_folder, output_folder=args.results_folder, ) From c31182517d75c1d714d92513e512f86034b81242 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 19 Nov 2023 16:44:27 +0000 Subject: [PATCH 268/443] hs: sort equipment for logging --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 5e3414d22c..d14f8f10ae 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -401,7 +401,7 @@ def as_namedtuple( beddays_footprint=tuple( sorted((k, v) for k, v in self.BEDDAYS_FOOTPRINT.items() if v > 0) ), - equipment=(tuple(self.EQUIPMENT)) + equipment=(tuple(sorted(self.EQUIPMENT))) ) From 811da1f825cc9686ee6079c3fcc360fb2517e161 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 19 Nov 2023 23:58:02 +0000 Subject: [PATCH 269/443] test_hs: assert equipment logging within detailed_hsi_event --- tests/test_healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_healthsystem.py b/tests/test_healthsystem.py index 62a39aea3c..f5b95c74c4 100644 --- a/tests/test_healthsystem.py +++ b/tests/test_healthsystem.py @@ -861,7 +861,7 @@ def apply(self, person_id, squeeze_factor): detailed_consumables = log["tlo.methods.healthsystem"]['Consumables'] assert {'date', 'TREATMENT_ID', 'did_run', 'Squeeze_Factor', 'priority', 'Number_By_Appt_Type_Code', 'Person_ID', - 'Facility_Level', 'Facility_ID', 'Event_Name', + 'Facility_Level', 'Facility_ID', 'Event_Name', 'equipment' } == set(detailed_hsi_event.columns) assert {'date', 'Frac_Time_Used_Overall', 'Frac_Time_Used_By_Facility_ID', 'Frac_Time_Used_By_OfficerType', } == set(detailed_capacity.columns) From 5c90ad4e47a05f9fa4c99608039f98163dbdaf6d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 23 Nov 2023 12:00:46 +0000 Subject: [PATCH 270/443] [no ci] hs: typo; Equipment logging removed diff --git src/tlo/methods/healthsystem.py src/tlo/methods/healthsystem.py index d14f8f10a..5ecb43368 100644 --- src/tlo/methods/healthsystem.py +++ src/tlo/methods/healthsystem.py @@ -2611,7 +2611,7 @@ class HealthSystemScheduler(RegularEvent, PopulationScopeEventMixin): squeeze_factor=0.0, priority=-1, did_run=True, - equipment=set() # TODO: explore more, should it be non-emtpy in some cases? + equipment=set(), # TODO: explore more, should it be non-empty in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2666,7 +2666,6 @@ class HealthSystemSummaryCounter: self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} # <--Same as `self._appts` but also split by facility_level - self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran @@ -2683,8 +2682,7 @@ class HealthSystemSummaryCounter: hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str, - equipment: set + level: str ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2701,9 +2699,6 @@ class HealthSystemSummaryCounter: self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number - # Update used equipment by level - self._equip_by_level[level].update(equipment) - def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2766,17 +2761,6 @@ class HealthSystemSummaryCounter: }, ) - # Sort equipment within levels, and log them - for key in self._equip_by_level: - self._equip_by_level[key] = sorted(self._equip_by_level[key]) - logger_summary.info( - key="Equipment", - description="Sets of used equipment for each facility level in this calendar year.", - data={ - "Equipment_By_Level": self._equip_by_level, - }, - ) - self._reset_internal_stores() --- src/tlo/methods/healthsystem.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index d14f8f10ae..5ecb43368c 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2611,7 +2611,7 @@ def apply(self, population): squeeze_factor=0.0, priority=-1, did_run=True, - equipment=set() # TODO: explore more, should it be non-emtpy in some cases? + equipment=set(), # TODO: explore more, should it be non-empty in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2666,7 +2666,6 @@ def _reset_internal_stores(self) -> None: self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} # <--Same as `self._appts` but also split by facility_level - self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran @@ -2683,8 +2682,7 @@ def record_hsi_event(self, hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str, - equipment: set + level: str ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2701,9 +2699,6 @@ def record_hsi_event(self, self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number - # Update used equipment by level - self._equip_by_level[level].update(equipment) - def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2766,17 +2761,6 @@ def write_to_log_and_reset_counters(self): }, ) - # Sort equipment within levels, and log them - for key in self._equip_by_level: - self._equip_by_level[key] = sorted(self._equip_by_level[key]) - logger_summary.info( - key="Equipment", - description="Sets of used equipment for each facility level in this calendar year.", - data={ - "Equipment_By_Level": self._equip_by_level, - }, - ) - self._reset_internal_stores() From e970814014c0a2c62bbc672970a4bac43f021844 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Dec 2023 23:16:05 +0000 Subject: [PATCH 271/443] equip_catalogue: fix the keys mapping to be done for each run --- .../equipment/equipment_catalogue.py | 157 ++++++++++++------ 1 file changed, 104 insertions(+), 53 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 9672c92474..1ba0f83cb4 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -1,14 +1,23 @@ import argparse +import warnings from pathlib import Path import pandas as pd -from tlo.analysis.utils import extract_results, load_pickled_dataframes +from tlo.analysis.utils import extract_results +# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# Declare whether to scale the counts to Malawi population size +do_scaling = True +# Declare output file names +output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' +output_file_name = 'equipment_annual_counts__by_Year_TreatmentID_FacLevel.csv' +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -def get_annual_hsi_event_counts(results_folder: Path) -> pd.DataFrame: - """Return pd.DataFrame gives the simulated annual counts of all the hsi event details logged (details as keys) - for each simulated year. + +def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: + """Returned pd.DataFrame gives the monthly counts of all the hsi event details logged (details as keys) + for each simulated month. NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" def get_hsi_event_counts(_df): @@ -46,68 +55,110 @@ def unpack_dict_in_series(_raw: pd.Series): results_folder, module='tlo.methods.healthsystem.summary', key='hsi_event_counts', - custom_generate_series=get_hsi_event_counts + custom_generate_series=get_hsi_event_counts, + do_scaling=do_scaling ) -def get_hsi_event_keys(results_folder: Path): - return load_pickled_dataframes(results_folder, 0, 0, "tlo.methods.healthsystem.summary")[ - "tlo.methods.healthsystem.summary" - ]["hsi_event_details"]["hsi_event_key_to_event_details"][0] +def get_hsi_event_keys_all_runs(results_folder: Path) -> pd.DataFrame: + """Returned pd.DataFrame gives the dictionaries of hsi_event_details for each draw and run. + NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" + def get_hsi_event_keys(_df): + """Get the hsi_event_keys for one particular run.""" + return _df['hsi_event_key_to_event_details'] -def create_equipment_catalogues(results_folder: Path, output_folder: Path): + return extract_results( + results_folder, + module='tlo.methods.healthsystem.summary', + key='hsi_event_details', + custom_generate_series=get_hsi_event_keys + ) - # Declare output file names - output_detailed_file_name = 'equipment_annual_counts__all_event_details.csv' - output_file_name = 'equipment_annual_counts__by_Date_EventName_FacLevel.csv' + +def create_equipment_catalogues(results_folder: Path, output_folder: Path): # %% Catalogue equipment by all HSI event details - sim_equipment = get_annual_hsi_event_counts(results_folder) + sim_equipment = get_monthly_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) - sim_equipment_df.fillna(0, inplace=True) - - hsi_event_keys = get_hsi_event_keys(results_folder) - - decoded_keys = sim_equipment_df.index.get_level_values(1).astype(str).map(hsi_event_keys) - sim_equipment_df = pd.concat([sim_equipment_df, pd.DataFrame(decoded_keys.tolist(), index=sim_equipment_df.index)], axis=1) - # Make values in 'appt_footprint', and 'beddays_footprint' columns to be string - sim_equipment_df['appt_footprint'] = sim_equipment_df['appt_footprint'].apply(lambda x: ', '.join(map(str, x))) - sim_equipment_df['beddays_footprint'] = sim_equipment_df['beddays_footprint'].apply(lambda x: ', '.join(map(str, x))) - # Explode the 'equipment' column - exploded_df = sim_equipment_df.explode('equipment') - # Remove the 'event_details_key' and replace the index with hsi event details as indexes - exploded_df = exploded_df.droplevel(level=1) - exploded_df = exploded_df.set_index( - ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', - 'equipment'], append=True - ) - # Sum values with the same multi-index - exploded_df = exploded_df.groupby(exploded_df.index).sum() - # Convert the index back to a MultiIndex - exploded_df.index = pd.MultiIndex.from_tuples(exploded_df.index) - exploded_df.index.names = \ - ['date', 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', - 'equipment'] - - # Save the detailed equipment catalogue by levels - exploded_df.to_csv(output_folder / output_detailed_file_name) + hsi_event_keys = get_hsi_event_keys_all_runs(results_folder) + + final_df = pd.DataFrame() + + def details_col_to_str(details_col): + return details_col.apply(lambda x: ', '.join(map(str, x))) + + for col in hsi_event_keys.columns: + df_col = sim_equipment_df[col].dropna() + decoded_keys = df_col.index.get_level_values(1).astype(str).map(hsi_event_keys.at[0, col]) + + # %%% Verify the keys in dictionary and dataframe for the run 'col' are same + # Check if all keys in hsi_event_keys_set are in the 'event_details_key' of df_col + hsi_event_keys_set = set(hsi_event_keys.at[0, col].keys()) + missing_keys_df =\ + [key for key in hsi_event_keys_set if key not in df_col.index.get_level_values('event_details_key')] + + # Check if all keys in the 'event_details_key' of df_col are in hsi_event_keys_set + missing_keys_dict =\ + [key for key in df_col.index.get_level_values('event_details_key') if key not in hsi_event_keys_set] + + # Warn if some keys are missing + if missing_keys_df: + warnings.warn(UserWarning(f"Keys missing in sim_equipment_df for the run {col}: {missing_keys_df}")) + + if missing_keys_dict: + warnings.warn(UserWarning(f"Keys missing in hsi_event_keys for the run {col}: {missing_keys_dict}")) + # %%% + + df_col = pd.concat([df_col, pd.DataFrame(decoded_keys.tolist(), index=df_col.index)], axis=1) + # Make values in 'appt_footprint', 'beddays_footprint' columns to be string + df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) + df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) + # Explode the 'equipment' column + exploded_df = df_col.explode('equipment') + # Remove the 'event_details_key' and replace the index with hsi event details as indexes + exploded_df = exploded_df.droplevel(level=1) + exploded_df = exploded_df.set_index( + ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', + 'equipment'], append=True + ) + # Sum values with the same multi-index (keep also empty indexes) + exploded_df = exploded_df.groupby(level=exploded_df.index.names, dropna=False).sum() + # Add the results for the run 'col' to final_df + final_df = pd.concat([final_df, exploded_df], axis=1) + + # Replace NaN with 0 + final_df.fillna(0, inplace=True) + # Save the detailed equipment catalogue + final_df.to_csv(output_folder / output_detailed_file_name) print(f'{output_detailed_file_name} saved.') # --- - # %% Catalogue equipment by Facility Levels and HSI Event Names - - equipment_counts_by_date_hsi_name_level_df = exploded_df.copy() - - # Sum counts for each equipment with the same date, hsi event name, and level (remaining indexes removed) - # equipment_counts_by_date_hsi_name_level_df - equipment_counts_by_date_hsi_name_level_df = \ - equipment_counts_by_date_hsi_name_level_df.groupby(['date', 'event_name', 'facility_level', 'equipment']).sum() - - # Save the CSV equipment counts - equipment_counts_by_date_hsi_name_level_df.to_csv(output_folder / output_file_name) + # %% Catalogue equipment by Treatment ID and Facility Levels + equipment_counts_by_date_treatment_id_level_df = final_df.copy() + + # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), + # keeping only non-empty 'equipment' indexes + equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( + ['date', 'treatment_id', 'facility_level', 'equipment'], + dropna=True + # TODO: make 'treatment_id', 'facility_level' to be an input + ).sum() + + # Sum counts annually + equipment_counts_by_date_treatment_id_level_df['year'] = \ + equipment_counts_by_date_treatment_id_level_df.index.get_level_values('date').year + # TODO: make annual/monthly results according to input + equipment_counts_by_date_treatment_id_level_df.set_index('year', append=True, inplace=True) + equipment_counts_by_date_treatment_id_level_df.index.droplevel('date') + equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( + ['year', 'treatment_id', 'facility_level', 'equipment'] + ).sum() + + # Save the equipment counts CSV + equipment_counts_by_date_treatment_id_level_df.to_csv(output_folder / output_file_name) print(f'{output_file_name} saved.') - # --- + # # --- return 0 From f4a91fbfcc5df5e2a5f8ead53b9cbcf8469213f8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 00:11:13 +0000 Subject: [PATCH 272/443] equip_catalogue: input hsi event details by which to catalog equipment --- .../equipment/equipment_catalogue.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 1ba0f83cb4..89d0c804c7 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -6,13 +6,23 @@ from tlo.analysis.utils import extract_results -# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Declare whether to scale the counts to Malawi population size +# (True/False) do_scaling = True -# Declare output file names +# Declare as list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) +# (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') +catalog_by = ['treatment_id', 'facility_level'] +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +# %%% Output file names +# detailed CSV name output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' -output_file_name = 'equipment_annual_counts__by_Year_TreatmentID_FacLevel.csv' -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# requested details only CSV name +output_file_name_prefix = 'equipment_annual_counts__by_year_' +output_file_name_suffix = '.csv' +output_file_name_details_specified = '_'.join(catalog_by) +output_file_name = output_file_name_prefix + output_file_name_details_specified + output_file_name_suffix def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: @@ -78,7 +88,7 @@ def get_hsi_event_keys(_df): def create_equipment_catalogues(results_folder: Path, output_folder: Path): - # %% Catalogue equipment by all HSI event details + # %% Catalog equipment by all HSI event details sim_equipment = get_monthly_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) hsi_event_keys = get_hsi_event_keys_all_runs(results_folder) @@ -134,15 +144,15 @@ def details_col_to_str(details_col): print(f'{output_detailed_file_name} saved.') # --- - # %% Catalogue equipment by Treatment ID and Facility Levels + # %% Catalog equipment by requested details equipment_counts_by_date_treatment_id_level_df = final_df.copy() # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), # keeping only non-empty 'equipment' indexes + to_be_grouped_by = ['date'] + catalog_by + ['equipment'] equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( - ['date', 'treatment_id', 'facility_level', 'equipment'], + to_be_grouped_by, dropna=True - # TODO: make 'treatment_id', 'facility_level' to be an input ).sum() # Sum counts annually From 81a6eff337c806e5f6c78738e8cf182dd8e1f9b0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 00:41:54 +0000 Subject: [PATCH 273/443] equip_catalogue: input time period by which to catalog equipment --- .../equipment/equipment_catalogue.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 89d0c804c7..da62e30afd 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -10,19 +10,22 @@ # Declare whether to scale the counts to Malawi population size # (True/False) do_scaling = True -# Declare as list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) +# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) # (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') -catalog_by = ['treatment_id', 'facility_level'] +catalog_by_details = ['treatment_id', 'facility_level'] +# Declare which time period you want the equipment be grouped in the catalogue (choose only one) +# (periods: 'monthly', 'annual') +catalog_by_time = 'annual' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# TODO: verify inputs are as expected # %%% Output file names # detailed CSV name output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' # requested details only CSV name -output_file_name_prefix = 'equipment_annual_counts__by_year_' -output_file_name_suffix = '.csv' -output_file_name_details_specified = '_'.join(catalog_by) -output_file_name = output_file_name_prefix + output_file_name_details_specified + output_file_name_suffix +time_index = 'year' if catalog_by_time == 'annual' else 'date' +output_file_name = \ + 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: @@ -145,28 +148,29 @@ def details_col_to_str(details_col): # --- # %% Catalog equipment by requested details - equipment_counts_by_date_treatment_id_level_df = final_df.copy() + equipment_counts_by_time_and_requested_details = final_df.copy() # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), # keeping only non-empty 'equipment' indexes - to_be_grouped_by = ['date'] + catalog_by + ['equipment'] - equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( + to_be_grouped_by = ['date'] + catalog_by_details + ['equipment'] + equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( to_be_grouped_by, dropna=True ).sum() - # Sum counts annually - equipment_counts_by_date_treatment_id_level_df['year'] = \ - equipment_counts_by_date_treatment_id_level_df.index.get_level_values('date').year - # TODO: make annual/monthly results according to input - equipment_counts_by_date_treatment_id_level_df.set_index('year', append=True, inplace=True) - equipment_counts_by_date_treatment_id_level_df.index.droplevel('date') - equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( - ['year', 'treatment_id', 'facility_level', 'equipment'] - ).sum() + if catalog_by_time == 'annual': + # Sum counts annually + equipment_counts_by_time_and_requested_details['year'] = \ + equipment_counts_by_time_and_requested_details.index.get_level_values('date').year + equipment_counts_by_time_and_requested_details.set_index('year', append=True, inplace=True) + equipment_counts_by_time_and_requested_details.index.droplevel('date') + to_be_grouped_by = ['year'] + catalog_by_details + ['equipment'] + equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( + to_be_grouped_by + ).sum() # Save the equipment counts CSV - equipment_counts_by_date_treatment_id_level_df.to_csv(output_folder / output_file_name) + equipment_counts_by_time_and_requested_details.to_csv(output_folder / output_file_name) print(f'{output_file_name} saved.') # # --- From d6fa91d7874e7fb53d0ac7dded5c5e8dc63aa3b9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 00:48:08 +0000 Subject: [PATCH 274/443] equip_catalogue: list of requested details can be empty --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index da62e30afd..d69bc9e638 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -10,7 +10,7 @@ # Declare whether to scale the counts to Malawi population size # (True/False) do_scaling = True -# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) +# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose any number) # (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') catalog_by_details = ['treatment_id', 'facility_level'] # Declare which time period you want the equipment be grouped in the catalogue (choose only one) From 903adcc874eb3d184347973968305600111df13b Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 01:44:39 +0000 Subject: [PATCH 275/443] equip_catalogue: verify inputs as expected & set output file names in 'create_equipment_catalogues' fnc --- .../equipment/equipment_catalogue.py | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index d69bc9e638..66fcff01ae 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -17,15 +17,6 @@ # (periods: 'monthly', 'annual') catalog_by_time = 'annual' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# TODO: verify inputs are as expected - -# %%% Output file names -# detailed CSV name -output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' -# requested details only CSV name -time_index = 'year' if catalog_by_time == 'annual' else 'date' -output_file_name = \ - 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: @@ -90,6 +81,27 @@ def get_hsi_event_keys(_df): def create_equipment_catalogues(results_folder: Path, output_folder: Path): + # %%% Verify inputs are as expected + assert isinstance(do_scaling, bool), "The input parameter 'do_scaling' must be a boolean (True or False)" + assert isinstance(catalog_by_details, list), "The input parameter 'catalog_by_details' must be a list" + event_details = \ + {'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint'} + for item in catalog_by_details: + assert isinstance(item, str) and item in event_details, \ + f"Each element in the input list 'catalog_by_details' must be a string and be one of the details:\n" \ + f"{event_details}" + assert catalog_by_time in {'monthly', 'annual'}, \ + "The input parameter 'catalog_by_time' must be one of the strings ('monthly' or 'annual')" + # --- + + # %%% Set output file names + # detailed CSV name + output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' + # requested details only CSV name + time_index = 'year' if catalog_by_time == 'annual' else 'date' + output_file_name = \ + 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' + # --- # %% Catalog equipment by all HSI event details sim_equipment = get_monthly_hsi_event_counts(results_folder) @@ -172,7 +184,7 @@ def details_col_to_str(details_col): # Save the equipment counts CSV equipment_counts_by_time_and_requested_details.to_csv(output_folder / output_file_name) print(f'{output_file_name} saved.') - # # --- + # --- return 0 From 1d9c5944af1df5f7e2be3d6eaa0b95edfb59348a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 16:54:53 +0000 Subject: [PATCH 276/443] equip_catalogue: (1) detailed - equip set as string in one row, module_name before event_name, sorted by indexes; (2) added summary catal. (3) focused - only this cat. without empty equip rows and split by item per row --- .../equipment/equipment_catalogue.py | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 66fcff01ae..6ba03916c8 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -99,8 +99,9 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' # requested details only CSV name time_index = 'year' if catalog_by_time == 'annual' else 'date' - output_file_name = \ + output_focused_file_name = \ 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' + output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id.csv' # --- # %% Catalog equipment by all HSI event details @@ -113,6 +114,12 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): def details_col_to_str(details_col): return details_col.apply(lambda x: ', '.join(map(str, x))) + def lists_of_strings_to_strings_of_list(list_of_strings_col): + return list_of_strings_col.apply(lambda x: "['" + "', '".join(map(str, x)) + "']") + + def strings_of_list_to_lists_of_strings(strings_of_list_col): + return strings_of_list_col.apply(lambda x: x.strip('][').split(', ')) + for col in hsi_event_keys.columns: df_col = sim_equipment_df[col].dropna() decoded_keys = df_col.index.get_level_values(1).astype(str).map(hsi_event_keys.at[0, col]) @@ -136,34 +143,38 @@ def details_col_to_str(details_col): # %%% df_col = pd.concat([df_col, pd.DataFrame(decoded_keys.tolist(), index=df_col.index)], axis=1) - # Make values in 'appt_footprint', 'beddays_footprint' columns to be string + # Make values in 'appt_footprint', 'beddays_footprint', and 'equipment' columns to be string df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) - # Explode the 'equipment' column - exploded_df = df_col.explode('equipment') - # Remove the 'event_details_key' and replace the index with hsi event details as indexes - exploded_df = exploded_df.droplevel(level=1) - exploded_df = exploded_df.set_index( - ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', - 'equipment'], append=True - ) - # Sum values with the same multi-index (keep also empty indexes) - exploded_df = exploded_df.groupby(level=exploded_df.index.names, dropna=False).sum() - # Add the results for the run 'col' to final_df - final_df = pd.concat([final_df, exploded_df], axis=1) + df_col['equipment'] = lists_of_strings_to_strings_of_list(df_col['equipment']) + df_col = (df_col.droplevel(level=1) + .set_index(['module_name', 'event_name', 'treatment_id', 'facility_level', 'appt_footprint', + 'beddays_footprint', 'equipment'], append=True)) + final_df = pd.concat([final_df, df_col], axis=1) # Replace NaN with 0 final_df.fillna(0, inplace=True) + final_df.sort_index(inplace=True) # Save the detailed equipment catalogue final_df.to_csv(output_folder / output_detailed_file_name) print(f'{output_detailed_file_name} saved.') # --- + # %% Catalog equipment summary + equipment_summary = final_df.copy() + equipment_summary = equipment_summary.groupby(['module_name', 'event_name', 'treatment_id', 'equipment']).sum() + equipment_summary = \ + equipment_summary.reset_index().set_index(['module_name', 'event_name', 'treatment_id', 'equipment']) + # Save the summary equipment catalogue + equipment_summary.index.to_frame().to_csv(output_folder / output_summary_file_name, index=False) + print(f'{output_summary_file_name} saved.') + # --- + # %% Catalog equipment by requested details equipment_counts_by_time_and_requested_details = final_df.copy() - # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), - # keeping only non-empty 'equipment' indexes + # Sum counts for each equipment set with the same date, treatment id, and facility level + # (remaining indexes removed), keeping only non-empty 'equipment' indexes to_be_grouped_by = ['date'] + catalog_by_details + ['equipment'] equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( to_be_grouped_by, @@ -181,9 +192,24 @@ def details_col_to_str(details_col): to_be_grouped_by ).sum() + # Remove rows with no equipment used + equipment_counts_by_time_and_requested_details.drop("['']", level='equipment', axis=0, inplace=True) + # Split the equipment by an item per row + equipment_counts_by_time_and_requested_details['equipment'] = \ + equipment_counts_by_time_and_requested_details.index.get_level_values('equipment') + equipment_counts_by_time_and_requested_details.index = \ + equipment_counts_by_time_and_requested_details.index.droplevel('equipment') + equipment_counts_by_time_and_requested_details['equipment'] = strings_of_list_to_lists_of_strings( + equipment_counts_by_time_and_requested_details['equipment'] + ) + exploded_df = equipment_counts_by_time_and_requested_details.explode('equipment') + exploded_df = exploded_df.set_index(['equipment'], append=True) + # Sum values with the same multi-index + exploded_df = exploded_df.groupby(level=exploded_df.index.names).sum() + # Save the equipment counts CSV - equipment_counts_by_time_and_requested_details.to_csv(output_folder / output_file_name) - print(f'{output_file_name} saved.') + exploded_df.to_csv(output_folder / output_focused_file_name) + print(f'{output_focused_file_name} saved.') # --- return 0 From df7479ee9c106d4b60f2f835d67607a8d7ce4137 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 21:30:58 +0000 Subject: [PATCH 277/443] equip_catalogue: suffix (as input) added for output file names --- .../healthsystem/equipment/equipment_catalogue.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 6ba03916c8..4506892e8b 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -16,6 +16,8 @@ # Declare which time period you want the equipment be grouped in the catalogue (choose only one) # (periods: 'monthly', 'annual') catalog_by_time = 'annual' +# Suffix for output file names +suffix_file_names = '__5y_20Kpop_10runs' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -96,12 +98,13 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): # %%% Set output file names # detailed CSV name - output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' + output_detailed_file_name = 'equipment_monthly_counts__all_event_details' + suffix_file_names + '.csv' # requested details only CSV name time_index = 'year' if catalog_by_time == 'annual' else 'date' output_focused_file_name = \ - 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' - output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id.csv' + 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + \ + suffix_file_names + '.csv' + output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id)' + suffix_file_names + '.csv' # --- # %% Catalog equipment by all HSI event details From c67618cce35b85b90fa4fedf8090756515293c4e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 10:26:11 +0000 Subject: [PATCH 278/443] hs: structure v2; alri+co: examples to test new structure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit diff --git src/tlo/methods/alri.py src/tlo/methods/alri.py index e4b0b46d1..c211e3317 100644 --- src/tlo/methods/alri.py +++ src/tlo/methods/alri.py @@ -2290,6 +2290,8 @@ class HSI_Alri_Treatment(HSI_Event, IndividualScopeEventMixin): self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels + self.set_essential_equipment({'Pulse oximeter'}) + # TODO: CORRECT --- an example with ess. equipm. set (which may or may not be used at the end) self.is_followup_following_treatment_failure = is_followup_following_treatment_failure if not inpatient: @@ -2596,6 +2598,8 @@ class HSI_Alri_Treatment(HSI_Event, IndividualScopeEventMixin): 'cough_or_cold' (symptoms-based assessment) }.""" + self.update_equipment({'Pulse oximeter'}) + child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) imci_classification_based_on_symptoms = self._get_imci_classification_based_on_symptoms( diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index f86e342c8..d6786dd39 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -353,7 +353,7 @@ class Contraception(Module): # Increase prob of 'female_sterilization' in older women accordingly probs_30plus = probs.copy() probs_30plus['female_sterilization'] = ( - probs.loc['female_sterilization'] / + probs.loc['female_sterilization'] / self.ratio_n_females_30_49_to_15_49_in_2010 ) # Scale so that the probability of all outcomes sum to 1.0 @@ -633,7 +633,7 @@ class Contraception(Module): # Increase prob of 'female_sterilization' in older women accordingly probs_30plus = probs.copy() probs_30plus['female_sterilization'] = ( - probs.loc['female_sterilization'] / + probs.loc['female_sterilization'] / self.ratio_n_females_30_49_to_15_49_in_2010 ) # Scale so that the probability of all outcomes sum to 1.0 @@ -1194,6 +1194,7 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level + self.set_essential_equipment({''}) @property def EXPECTED_APPT_FOOTPRINT(self): @@ -1229,6 +1230,11 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) # 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.update_equipment({ + 'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine' + }) + # Determine essential and optional items # TODO: we don't distinguish essential X optional for contraception methods yet, will need to update once we do items_essential = self.module.cons_codes[self.new_contraceptive] @@ -1255,7 +1261,8 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) 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: @@ -1280,6 +1287,16 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) _new_contraceptive = self.new_contraceptive + # Update equipment if any needed for the method + if _new_contraceptive == 'female_sterilization': + self.update_equipment({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' + }) + elif _new_contraceptive == 'IUD': + self.update_equipment({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' + }) + else: _new_contraceptive = "not_using" diff --git src/tlo/methods/healthsystem.py src/tlo/methods/healthsystem.py index 5ecb43368..1ad875f67 100644 --- src/tlo/methods/healthsystem.py +++ src/tlo/methods/healthsystem.py @@ -183,6 +183,7 @@ class HSI_Event: self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + # self.set_essential_equipment({''}) # HSI needs this attribute, but it is not defined in the Base class self.EQUIPMENT = set() @property @@ -339,6 +340,37 @@ class HSI_Event: "values" ) + def set_essential_equipment(self, set_of_equip): + """Helper function to set essential equipment. + + Should be passed a set of equipment items names (strings) or an empty set. + """ + # Set EQUIPMENT if the given set_of_equip in correct format, ie a set of strings or an empty set + if isinstance(set_of_equip, set) and all(isinstance(item, str) for item in set_of_equip): + self.ESSENTIAL_EQUIPMENT = set_of_equip + return 0 + + raise ValueError( + "Argument to set_essential_equipment should be an empty set or a set of strings of equipment item names " + "from ResourceFile_Equipment.csv." + ) + + def update_equipment(self, set_of_equip): + """Helper function to update equipment. + + Should be passed a set of equipment item names (strings). + """ + # Update EQUIPMENT if the given set_of_equip in correct format, ie a non-empty set of strings + if isinstance(set_of_equip, set) and (all(isinstance(item, str) for item in set_of_equip)) and \ + (set_of_equip not in [set(), None, {''}]): + self.EQUIPMENT.update(set_of_equip) + return self.EQUIPMENT.discard('') + + raise ValueError( + "Argument to update_equipment should be a non-empty set of strings of equipment item names " + "from ResourceFile_Equipment.csv." + ) + def initialise(self): """Initialise the HSI: * Set the facility_info --- src/tlo/methods/alri.py | 4 ++++ src/tlo/methods/contraception.py | 23 ++++++++++++++++++++--- src/tlo/methods/healthsystem.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index e4b0b46d17..c211e33179 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2290,6 +2290,8 @@ def __init__(self, module: Module, person_id: int, facility_level: str = "0", in self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels + self.set_essential_equipment({'Pulse oximeter'}) + # TODO: CORRECT --- an example with ess. equipm. set (which may or may not be used at the end) self.is_followup_following_treatment_failure = is_followup_following_treatment_failure if not inpatient: @@ -2596,6 +2598,8 @@ def _get_disease_classification_for_treatment_decision(self, 'cough_or_cold' (symptoms-based assessment) }.""" + self.update_equipment({'Pulse oximeter'}) + child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) imci_classification_based_on_symptoms = self._get_imci_classification_based_on_symptoms( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index f86e342c83..d6786dd39b 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -353,7 +353,7 @@ def avoid_sterilization_below30(probs): # Increase prob of 'female_sterilization' in older women accordingly probs_30plus = probs.copy() probs_30plus['female_sterilization'] = ( - probs.loc['female_sterilization'] / + probs.loc['female_sterilization'] / self.ratio_n_females_30_49_to_15_49_in_2010 ) # Scale so that the probability of all outcomes sum to 1.0 @@ -633,7 +633,7 @@ def avoid_sterilization_below30(probs): # Increase prob of 'female_sterilization' in older women accordingly probs_30plus = probs.copy() probs_30plus['female_sterilization'] = ( - probs.loc['female_sterilization'] / + probs.loc['female_sterilization'] / self.ratio_n_females_30_49_to_15_49_in_2010 ) # Scale so that the probability of all outcomes sum to 1.0 @@ -1194,6 +1194,7 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level + self.set_essential_equipment({''}) @property def EXPECTED_APPT_FOOTPRINT(self): @@ -1229,6 +1230,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.update_equipment({ + 'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine' + }) + # Determine essential and optional items # TODO: we don't distinguish essential X optional for contraception methods yet, will need to update once we do items_essential = self.module.cons_codes[self.new_contraceptive] @@ -1255,7 +1261,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: @@ -1280,6 +1287,16 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive + # Update equipment if any needed for the method + if _new_contraceptive == 'female_sterilization': + self.update_equipment({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' + }) + elif _new_contraceptive == 'IUD': + self.update_equipment({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' + }) + else: _new_contraceptive = "not_using" diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 5ecb43368c..1ad875f679 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -183,6 +183,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + # self.set_essential_equipment({''}) # HSI needs this attribute, but it is not defined in the Base class self.EQUIPMENT = set() @property @@ -339,6 +340,37 @@ def make_appt_footprint(self, dict_of_appts): "values" ) + def set_essential_equipment(self, set_of_equip): + """Helper function to set essential equipment. + + Should be passed a set of equipment items names (strings) or an empty set. + """ + # Set EQUIPMENT if the given set_of_equip in correct format, ie a set of strings or an empty set + if isinstance(set_of_equip, set) and all(isinstance(item, str) for item in set_of_equip): + self.ESSENTIAL_EQUIPMENT = set_of_equip + return 0 + + raise ValueError( + "Argument to set_essential_equipment should be an empty set or a set of strings of equipment item names " + "from ResourceFile_Equipment.csv." + ) + + def update_equipment(self, set_of_equip): + """Helper function to update equipment. + + Should be passed a set of equipment item names (strings). + """ + # Update EQUIPMENT if the given set_of_equip in correct format, ie a non-empty set of strings + if isinstance(set_of_equip, set) and (all(isinstance(item, str) for item in set_of_equip)) and \ + (set_of_equip not in [set(), None, {''}]): + self.EQUIPMENT.update(set_of_equip) + return self.EQUIPMENT.discard('') + + raise ValueError( + "Argument to update_equipment should be a non-empty set of strings of equipment item names " + "from ResourceFile_Equipment.csv." + ) + def initialise(self): """Initialise the HSI: * Set the facility_info From 422bfbf5279d7454a9199526920875842c4c2b59 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 10:27:05 +0000 Subject: [PATCH 279/443] equip_catalogue: TODO added --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 4506892e8b..877aa7460b 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -21,6 +21,7 @@ # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# TODO: Could I have use the bin_hsi_event_details from src/tlo/analysis/utils.py instead? If so, how? def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: """Returned pd.DataFrame gives the monthly counts of all the hsi event details logged (details as keys) for each simulated month. From 0eaacc360eed44bf1cfcdf0c172d305a347d6116 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 22:39:54 +0000 Subject: [PATCH 280/443] equip_catalogue: typo --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 877aa7460b..dd2dde8015 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -105,7 +105,7 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): output_focused_file_name = \ 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + \ suffix_file_names + '.csv' - output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id)' + suffix_file_names + '.csv' + output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id' + suffix_file_names + '.csv' # --- # %% Catalog equipment by all HSI event details From d99310a135db7d4ed36150de1ccacd1d1a5d6bf7 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 11:08:04 +0000 Subject: [PATCH 281/443] equip_catalogue: TODO added --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index dd2dde8015..4b91510c61 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -6,6 +6,7 @@ from tlo.analysis.utils import extract_results +# TODO: make these to be arguments of called fnc # %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Declare whether to scale the counts to Malawi population size # (True/False) From 9d31842a5785f019aee3f025a2f43d7d0d1693b9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 11:37:07 +0000 Subject: [PATCH 282/443] equip_catalogue: bug fixed --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 4b91510c61..4f47af6d0d 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -18,7 +18,7 @@ # (periods: 'monthly', 'annual') catalog_by_time = 'annual' # Suffix for output file names -suffix_file_names = '__5y_20Kpop_10runs' +suffix_file_names = '__2y_2Kpop_4runs_1draw' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -123,7 +123,8 @@ def lists_of_strings_to_strings_of_list(list_of_strings_col): return list_of_strings_col.apply(lambda x: "['" + "', '".join(map(str, x)) + "']") def strings_of_list_to_lists_of_strings(strings_of_list_col): - return strings_of_list_col.apply(lambda x: x.strip('][').split(', ')) + lists_of_strings_col = strings_of_list_col.apply(lambda x: x.strip('][').split("'")) + return lists_of_strings_col.apply(lambda x: [s for s in x if (s != '' and s != ', ')]) for col in hsi_event_keys.columns: df_col = sim_equipment_df[col].dropna() From 2c5a436023fd74e2d81f82902ad9e497f09071c6 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 18:57:30 +0000 Subject: [PATCH 283/443] brc: TODO added --- src/tlo/methods/breast_cancer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1ce9ad2bf6..2529911d84 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,6 +646,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + # TODO: but the appt footprints suggests mammography to be provided def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 14f426a90ecdbdebc851794c94a62544538d61a4 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 19:00:02 +0000 Subject: [PATCH 284/443] hs: rm prints --- src/tlo/methods/healthsystem.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1ad875f679..c792860600 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1806,8 +1806,6 @@ def write_to_hsi_log( description="record of each HSI event" ) if did_run: - print("\nevent_details") - print(event_details) if self._hsi_event_count_log_period is not None: event_details_key = self._hsi_event_details.setdefault( event_details, len(self._hsi_event_details) From e9c09ec707d74b06db4f237e0781a1572c784989 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 19:01:28 +0000 Subject: [PATCH 285/443] hs: rm old code --- src/tlo/methods/healthsystem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index c792860600..90119cb7d1 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1817,7 +1817,6 @@ def write_to_hsi_log( squeeze_factor=squeeze_factor, appt_footprint=event_details.appt_footprint, level=event_details.facility_level, - equipment=equipment, ) def call_and_record_never_ran_hsi_event(self, hsi_event, priority=None): From b88f2519306e5668256c1800668f9af5285c4614 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 19:04:14 +0000 Subject: [PATCH 286/443] hs: rm/add accidentally added/rmd commas --- src/tlo/methods/healthsystem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 90119cb7d1..0e8f46797c 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1849,7 +1849,7 @@ def write_to_never_ran_hsi_log( event_details: HSIEventDetails, person_id: int, facility_id: Optional[int], - priority: int + priority: int, ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1874,7 +1874,7 @@ def write_to_never_ran_hsi_log( treatment_id=event_details.treatment_id, hsi_event_name=event_details.event_name, appt_footprint=event_details.appt_footprint, - level=event_details.facility_level + level=event_details.facility_level, ) def log_current_capabilities_and_usage(self): @@ -2134,7 +2134,7 @@ def run_individual_level_events_in_mode_0_or_1(self, actual_appt_footprint=actual_appt_footprint, squeeze_factor=squeeze_factor, did_run=True, - priority=_priority, + priority=_priority ) # if not ok_to_run From 507eb4ec1a79e9c85e0271e75586b8d44fb81df2 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 15 Jan 2024 09:16:26 +0000 Subject: [PATCH 287/443] fixes in bladder_cancer.pyr --- src/tlo/methods/bladder_cancer.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 8b0b718d8a..1d23c6202c 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -758,9 +758,11 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check consumables are available - cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['screening_cystoscopy_core'], - optional_item_codes= - self.module.item_codes_breast_can['screening_biopsy_optional']) + # 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']) + if cons_avail: # Use a biopsy to diagnose whether the person has breast Cancer # If consumables are available log the use of equipment and run the dx_test representing the biopsy From 68ae9b07c025745db8eff32c3f2cbe434d383108 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 11:08:04 +0000 Subject: [PATCH 288/443] equip_catalogue: TODO added --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 3f5ee995ba..652bfa27cb 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -6,6 +6,7 @@ from tlo.analysis.utils import extract_results +# TODO: make these to be arguments of called fnc # %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Declare whether to scale the counts to Malawi population size # (True/False) From 7fb9ee82ce342508286e35dd204d0ba5835952a7 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 11:37:07 +0000 Subject: [PATCH 289/443] equip_catalogue: bug fixed --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 652bfa27cb..fd4960a32f 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -18,7 +18,7 @@ # (periods: 'monthly', 'annual') catalog_by_time = 'annual' # Suffix for output file names -suffix_file_names = '__5y_20Kpop_10runs' +suffix_file_names = '__2y_2Kpop_4runs_1draw' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -122,7 +122,8 @@ def lists_of_strings_to_strings_of_list(list_of_strings_col): return list_of_strings_col.apply(lambda x: "['" + "', '".join(map(str, x)) + "']") def strings_of_list_to_lists_of_strings(strings_of_list_col): - return strings_of_list_col.apply(lambda x: x.strip('][').split(', ')) + lists_of_strings_col = strings_of_list_col.apply(lambda x: x.strip('][').split("'")) + return lists_of_strings_col.apply(lambda x: [s for s in x if (s != '' and s != ', ')]) for col in hsi_event_keys.columns: df_col = sim_equipment_df[col].dropna() From c49dce01d0aaa11cad0799c8dd22af2a5a726ec8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 22:25:52 +0000 Subject: [PATCH 290/443] [no ci] cancers: TODOs-surgical pkg --- src/tlo/methods/bladder_cancer.py | 1 + src/tlo/methods/breast_cancer.py | 1 + src/tlo/methods/oesophagealcancer.py | 1 + src/tlo/methods/other_adult_cancers.py | 1 + src/tlo/methods/prostate_cancer.py | 1 + 5 files changed, 5 insertions(+) diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 1d23c6202c..f71f7156f0 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -859,6 +859,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available and the treatment will go ahead - update the equipment + # TODO: link to surgical equipment package when that exists self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 7569d34e72..d14f75de4a 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -790,6 +790,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment + # TODO: link to surgical equipment package when that exists self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index b27acce836..8ec51f6ce7 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -769,6 +769,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available and the treatment will go ahead - update the equipment + # TODO: link to surgical equipment package when that exists self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 95c708c98d..aadf289388 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -759,6 +759,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment + # TODO: link to surgical equipment package when that exists self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 311ea444d4..2cf277ec5a 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -897,6 +897,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment + # TODO: link to surgical equipment package when that exists self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) From a82704eaf82fd34c0cf8b4690fa42ec9bd4ce973 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 15 Jan 2024 18:19:11 +0000 Subject: [PATCH 291/443] bc: typos fixed --- src/tlo/methods/bladder_cancer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index f71f7156f0..ab573a5972 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -684,10 +684,10 @@ def apply(self, person_id, squeeze_factor): # 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_breast_can['screening_biopsy_optional']) + self.module.item_codes_bladder_can['screening_biopsy_optional']) if cons_avail: - # Use a biopsy to diagnose whether the person has breast Cancer + # Use a biopsy to diagnose whether the person has bladder Cancer # If consumables are available update the use of equipment and run the dx_test representing the biopsy self.EQUIPMENT.update({'Cystoscope', 'Ordinary Microscope'}) @@ -764,7 +764,7 @@ def apply(self, person_id, squeeze_factor): 'screening_biopsy_optional']) if cons_avail: - # Use a biopsy to diagnose whether the person has breast Cancer + # Use a biopsy to diagnose whether the person has bladder Cancer # If consumables are available log the use of equipment and run the dx_test representing the biopsy self.EQUIPMENT.update({'Cystoscope', 'Ordinary Microscope'}) @@ -855,7 +855,7 @@ def apply(self, person_id, squeeze_factor): # Check consumables are available cons_avail = self.get_consumables(item_codes=self.module.item_codes_bladder_can['treatment_surgery_core'], optional_item_codes= - self.module.item_codes_breast_can['treatment_surgery_optional']) + self.module.item_codes_bladder_can['treatment_surgery_optional']) if cons_avail: # If consumables are available and the treatment will go ahead - update the equipment From 3d9a2c386c908762ea5dc1d8e0252103c1eb1e4a Mon Sep 17 00:00:00 2001 From: tdm32 Date: Tue, 14 Nov 2023 16:19:52 +0000 Subject: [PATCH 292/443] add equipment for measles HSI if treatment for pneumonia is given --- src/tlo/methods/measles.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index a52cfd199a..278fa9207b 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -418,6 +418,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() # initialise empty set def apply(self, person_id, squeeze_factor): logger.debug(key="HSI_Measles_Treatment", @@ -436,6 +437,8 @@ def apply(self, person_id, squeeze_factor): # for measles with pneumonia if "respiratory_symptoms" in symptoms: item_codes.append(self.module.consumables['severe_pneumonia']) + # Update equipment + self.EQUIPMENT.update({'Oxygen concentrator', 'Oxygen cylinder, with regulator'}) # request the treatment if self.get_consumables(item_codes): From 92d11cbdd5d13e10f101385c45cb16fc93065d37 Mon Sep 17 00:00:00 2001 From: tdm32 Date: Tue, 14 Nov 2023 16:22:57 +0000 Subject: [PATCH 293/443] add in equipment use for schisto diagnosis --- src/tlo/methods/schisto.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tlo/methods/schisto.py b/src/tlo/methods/schisto.py index 2a7c94eed1..081f1be54a 100644 --- a/src/tlo/methods/schisto.py +++ b/src/tlo/methods/schisto.py @@ -899,6 +899,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Schisto_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD' if under_5 else 'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() # initialise empty set self._num_occurrences = 0 def apply(self, person_id, squeeze_factor): @@ -916,6 +917,8 @@ def apply(self, person_id, squeeze_factor): ) if will_test: + self.EQUIPMENT.update({'Ordinary Microscope'}) + # Determine if they truly are infected (with any of the species) is_infected = (person.loc[cols_of_infection_status] != 'Non-infected').any() @@ -957,6 +960,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Schisto_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD' if under_5 else 'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() # initialise empty set def apply(self, person_id, squeeze_factor): """Do the treatment for this person.""" @@ -987,6 +991,7 @@ def __init__(self, module, person_id, beneficiaries_ids: Optional[Sequence] = No # `self.EXPECTED_APPT_FOOTPRINT` show that this requires 1 * that appointment type. self.ACCEPTED_FACILITY_LEVEL = '1a' + self.EQUIPMENT = set() # initialise empty set def apply(self, person_id, squeeze_factor): """Provide the treatment to the beneficiaries of this HSI.""" From de2a7c386e90bc8dbb4f22346c8f68fb74a1e2a8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Dec 2023 22:48:14 +0000 Subject: [PATCH 294/443] me: within HSI_Me_Treatment equip for me. with pneumonia updated only if essen. consumbales avail. --- src/tlo/methods/measles.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index 278fa9207b..2ca79dea3a 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -437,14 +437,16 @@ def apply(self, person_id, squeeze_factor): # for measles with pneumonia if "respiratory_symptoms" in symptoms: item_codes.append(self.module.consumables['severe_pneumonia']) - # Update equipment - self.EQUIPMENT.update({'Oxygen concentrator', 'Oxygen cylinder, with regulator'}) # request the treatment if self.get_consumables(item_codes): logger.debug(key="HSI_Measles_Treatment", data=f"HSI_Measles_Treatment: giving required measles treatment to person {person_id}") + if "respiratory_symptoms" in symptoms: + # Update equipment + self.EQUIPMENT.update({'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 From 0111625c327d51757d43954c28a4bc23fae68eb8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 15 Jan 2024 16:36:57 +0000 Subject: [PATCH 295/443] hs & alri+co: rename and correct return of fncs related to equipment --- src/tlo/methods/alri.py | 4 ++-- src/tlo/methods/contraception.py | 8 +++---- src/tlo/methods/healthsystem.py | 38 +++++++++++++++++--------------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index c211e33179..640d4ec054 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2290,7 +2290,7 @@ def __init__(self, module: Module, person_id: int, facility_level: str = "0", in self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels - self.set_essential_equipment({'Pulse oximeter'}) + self.set_equipment_essential_to_run_event({'Pulse oximeter'}) # TODO: CORRECT --- an example with ess. equipm. set (which may or may not be used at the end) self.is_followup_following_treatment_failure = is_followup_following_treatment_failure @@ -2598,7 +2598,7 @@ def _get_disease_classification_for_treatment_decision(self, 'cough_or_cold' (symptoms-based assessment) }.""" - self.update_equipment({'Pulse oximeter'}) + self.add_equipment({'Pulse oximeter'}) child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index d6786dd39b..6fcae5817b 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1194,7 +1194,7 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level - self.set_essential_equipment({''}) + self.set_equipment_essential_to_run_event({''}) @property def EXPECTED_APPT_FOOTPRINT(self): @@ -1231,7 +1231,7 @@ def apply(self, person_id, squeeze_factor): 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.update_equipment({ + self.add_equipment({ 'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine' }) @@ -1289,11 +1289,11 @@ def apply(self, person_id, squeeze_factor): # Update equipment if any needed for the method if _new_contraceptive == 'female_sterilization': - self.update_equipment({ + self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' }) elif _new_contraceptive == 'IUD': - self.update_equipment({ + self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' }) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 0e8f46797c..23639d333f 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -183,7 +183,10 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - # self.set_essential_equipment({''}) # HSI needs this attribute, but it is not defined in the Base class + # self.set_equipment_essential_to_run_event({''}) # HSI needs this attribute, but it is not defined in the Base + # class to allow verification of its existence as a test for + # each HSI event, showing that equipment setup was thought + # through for the event. self.EQUIPMENT = set() @property @@ -340,36 +343,35 @@ def make_appt_footprint(self, dict_of_appts): "values" ) - def set_essential_equipment(self, set_of_equip): + def set_equipment_essential_to_run_event(self, set_of_equip): """Helper function to set essential equipment. Should be passed a set of equipment items names (strings) or an empty set. """ # Set EQUIPMENT if the given set_of_equip in correct format, ie a set of strings or an empty set - if isinstance(set_of_equip, set) and all(isinstance(item, str) for item in set_of_equip): - self.ESSENTIAL_EQUIPMENT = set_of_equip - return 0 + if not isinstance(set_of_equip, set) or any(not isinstance(item, str) for item in set_of_equip): + raise ValueError( + "Argument to set_equipment_essential_to_run_event should be an empty set or a set of strings of " + "equipment item names from ResourceFile_Equipment.csv." + ) - raise ValueError( - "Argument to set_essential_equipment should be an empty set or a set of strings of equipment item names " - "from ResourceFile_Equipment.csv." - ) + self.ESSENTIAL_EQUIPMENT = set_of_equip - def update_equipment(self, set_of_equip): + def add_equipment(self, set_of_equip): """Helper function to update equipment. Should be passed a set of equipment item names (strings). """ # Update EQUIPMENT if the given set_of_equip in correct format, ie a non-empty set of strings - if isinstance(set_of_equip, set) and (all(isinstance(item, str) for item in set_of_equip)) and \ - (set_of_equip not in [set(), None, {''}]): - self.EQUIPMENT.update(set_of_equip) - return self.EQUIPMENT.discard('') + if not isinstance(set_of_equip, set) or any(not isinstance(item, str) for item in set_of_equip) or \ + (set_of_equip in [set(), None, {''}]): + raise ValueError( + "Argument to add_equipment should be a non-empty set of strings of " + "equipment item names from ResourceFile_Equipment.csv." + ) - raise ValueError( - "Argument to update_equipment should be a non-empty set of strings of equipment item names " - "from ResourceFile_Equipment.csv." - ) + self.EQUIPMENT.update(set_of_equip) + self.EQUIPMENT.discard('') def initialise(self): """Initialise the HSI: From dc21979c4a2a70b9cee89bb395f6ecc6f4bb4134 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 16 Jan 2024 17:44:06 +0000 Subject: [PATCH 296/443] hs: log equip item codes instead of names --- src/tlo/methods/healthsystem.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 23639d333f..1e09db4de0 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -343,6 +343,10 @@ def make_appt_footprint(self, dict_of_appts): "values" ) + def get_equip_item_code_from_item_name(self, lookup_df: pd.DataFrame, equip_item_name: str) -> int: + """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" + return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) + def set_equipment_essential_to_run_event(self, set_of_equip): """Helper function to set essential equipment. @@ -369,8 +373,13 @@ def add_equipment(self, set_of_equip): "Argument to add_equipment should be a non-empty set of strings of " "equipment item names from ResourceFile_Equipment.csv." ) - - self.EQUIPMENT.update(set_of_equip) + # from the set of equip item names create a set of item codes + # this function is calling parameters from this + equip_codes = set(self.get_equip_item_code_from_item_name( + self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'], item_name + ) for item_name in set_of_equip + ) + self.EQUIPMENT.update(equip_codes) self.EQUIPMENT.discard('') def initialise(self): @@ -561,6 +570,9 @@ class HealthSystem(Module): "Availability of beds. If 'default' then use the availability specified in the ResourceFile; if " "'none', then let no beds be ever be available; if 'all', then all beds are always available. NB. This " "parameter is over-ridden if an argument is provided to the module initialiser."), + 'equip_item_and_package_code_lookups': Parameter( + Types.DATA_FRAME, "Items based on the the HSSP III 1K Equipment Costing (SEL Costing Sheet): " + "https://www.health.gov.mw/download/hssp-iii/, packages created in consultation with clinicians."), # Service Availability 'Service_Availability': Parameter( @@ -825,6 +837,10 @@ def read_parameters(self, data_folder): self.parameters['BedCapacity'] = pd.read_csv( path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Bed_Capacity.csv') + # Read in ResourceFile_Equipment + self.parameters['equip_item_and_package_code_lookups'] = pd.read_csv( + path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Equipment.csv') + # Data on the priority of each Treatment_ID that should be adopted in the queueing system according to different # priority policies. Load all policies at this stage, and decide later which one to adopt. self.parameters['priority_rank'] = pd.read_excel(path_to_resourcefiles_for_healthsystem / 'priority_policies' / From cdaa44a3e5c7b94c7b96292e95cfeca5f4531ce8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 17 Jan 2024 17:41:47 +0000 Subject: [PATCH 297/443] equip_catalogue: updated for logged equip item codes --- .../equipment/equipment_catalogue.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 4f47af6d0d..aa4552e536 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -109,6 +109,12 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id' + suffix_file_names + '.csv' # --- + # %%% Load RF + # Equipment + equip_resource_items_pkgs_df = pd.read_csv( + 'resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv' + ) + # %% Catalog equipment by all HSI event details sim_equipment = get_monthly_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) @@ -119,8 +125,15 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): def details_col_to_str(details_col): return details_col.apply(lambda x: ', '.join(map(str, x))) - def lists_of_strings_to_strings_of_list(list_of_strings_col): - return list_of_strings_col.apply(lambda x: "['" + "', '".join(map(str, x)) + "']") + def get_equip_item_name_from_item_code(lookup_df: pd.DataFrame, equip_item_code: str) -> int: + """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" + return str(pd.unique(lookup_df.loc[lookup_df["Equip_Code"] == equip_item_code, "Equip_Item"])[0]) + + def lists_of_equip_item_codes_to_strings_of_list_of_equip_item_names(list_of_equip_item_codes_col): + return list_of_equip_item_codes_col.apply( + lambda x: + str(sorted([get_equip_item_name_from_item_code(equip_resource_items_pkgs_df, item_code) for item_code in x])) + ) def strings_of_list_to_lists_of_strings(strings_of_list_col): lists_of_strings_col = strings_of_list_col.apply(lambda x: x.strip('][').split("'")) @@ -152,7 +165,7 @@ def strings_of_list_to_lists_of_strings(strings_of_list_col): # Make values in 'appt_footprint', 'beddays_footprint', and 'equipment' columns to be string df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) - df_col['equipment'] = lists_of_strings_to_strings_of_list(df_col['equipment']) + df_col['equipment'] = lists_of_equip_item_codes_to_strings_of_list_of_equip_item_names(df_col['equipment']) df_col = (df_col.droplevel(level=1) .set_index(['module_name', 'event_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', 'equipment'], append=True)) @@ -199,8 +212,7 @@ def strings_of_list_to_lists_of_strings(strings_of_list_col): ).sum() # Remove rows with no equipment used - equipment_counts_by_time_and_requested_details.drop("['']", level='equipment', axis=0, inplace=True) - # Split the equipment by an item per row + equipment_counts_by_time_and_requested_details.drop("[]", level='equipment', axis=0, inplace=True) equipment_counts_by_time_and_requested_details['equipment'] = \ equipment_counts_by_time_and_requested_details.index.get_level_values('equipment') equipment_counts_by_time_and_requested_details.index = \ @@ -230,4 +242,5 @@ def strings_of_list_to_lists_of_strings(strings_of_list_col): results_folder=args.results_folder, output_folder=args.results_folder, ) -# NB. Edit run configuration, the Parameters: "./outputs/sejjej5@ucl.ac.uk/long_run_all_diseases-2023-09-04T233551Z" +# NB. Edit run configuration, the Parameters: +# "./outputs/sejjej5@ucl.ac.uk/equip_jobs/long_run_all_diseases-2023-09-04T233551Z" From 287ee67783fe60e5ede3b1cbd704bab0bcaeda05 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 19 Jan 2024 13:58:10 +0000 Subject: [PATCH 298/443] brc: change Andrew suggested --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 2529911d84..47f583ff64 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -663,7 +663,7 @@ def apply(self, person_id, squeeze_factor): if not pd.isnull(df.at[person_id, "brc_date_diagnosis"]): return hs.get_blank_appt_footprint() - df.brc_breast_lump_discernible_investigated = True + df.at[person_id, 'brc_breast_lump_discernible_investigated'] = True # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this From 812a9e2a1d24611118b1a51d8228d2e778034a53 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 19 Jan 2024 14:53:41 +0000 Subject: [PATCH 299/443] hs: updates for better readability; rm unused code --- src/tlo/methods/healthsystem.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1e09db4de0..60a74ec4cf 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -6,7 +6,7 @@ from collections.abc import Iterable from itertools import repeat from pathlib import Path -from typing import Dict, List, NamedTuple, Optional, Tuple, Union +from typing import Dict, List, NamedTuple, Optional, Tuple, Union, Set import numpy as np import pandas as pd @@ -347,7 +347,7 @@ def get_equip_item_code_from_item_name(self, lookup_df: pd.DataFrame, equip_item """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) - def set_equipment_essential_to_run_event(self, set_of_equip): + def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: """Helper function to set essential equipment. Should be passed a set of equipment items names (strings) or an empty set. @@ -361,7 +361,7 @@ def set_equipment_essential_to_run_event(self, set_of_equip): self.ESSENTIAL_EQUIPMENT = set_of_equip - def add_equipment(self, set_of_equip): + def add_equipment(self, set_of_equip: Set[str]) -> None: """Helper function to update equipment. Should be passed a set of equipment item names (strings). @@ -375,12 +375,13 @@ def add_equipment(self, set_of_equip): ) # from the set of equip item names create a set of item codes # this function is calling parameters from this - equip_codes = set(self.get_equip_item_code_from_item_name( - self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'], item_name - ) for item_name in set_of_equip + equip_codes = set( + self.get_equip_item_code_from_item_name( + self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'], + item_name + ) for item_name in set_of_equip ) self.EQUIPMENT.update(equip_codes) - self.EQUIPMENT.discard('') def initialise(self): """Initialise the HSI: From b70b2e43fb9b0db01231747f7cad638e679579d2 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 19 Jan 2024 15:59:53 +0000 Subject: [PATCH 300/443] hs: PEP8 --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 60a74ec4cf..6bf80ed0f0 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -6,7 +6,7 @@ from collections.abc import Iterable from itertools import repeat from pathlib import Path -from typing import Dict, List, NamedTuple, Optional, Tuple, Union, Set +from typing import Dict, List, NamedTuple, Optional, Set, Tuple, Union import numpy as np import pandas as pd From 8cd81d009677e0b9e8a5d9e437d79a04e489be6a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 19 Jan 2024 22:45:00 +0000 Subject: [PATCH 301/443] hs: get_equip_item_code_from_item_name fnc updated; ESS.EQUIP as codes --- src/tlo/methods/healthsystem.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 6bf80ed0f0..c83854d06a 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -343,8 +343,9 @@ def make_appt_footprint(self, dict_of_appts): "values" ) - def get_equip_item_code_from_item_name(self, lookup_df: pd.DataFrame, equip_item_name: str) -> int: + def get_equip_item_code_from_item_name(self, equip_item_name: str) -> int: """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" + lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'] return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: @@ -359,7 +360,11 @@ def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: "equipment item names from ResourceFile_Equipment.csv." ) - self.ESSENTIAL_EQUIPMENT = set_of_equip + if set_of_equip not in [set(), None, {''}]: + equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) + self.ESSENTIAL_EQUIPMENT = equip_codes + else: + self.ESSENTIAL_EQUIPMENT = set() def add_equipment(self, set_of_equip: Set[str]) -> None: """Helper function to update equipment. @@ -375,12 +380,7 @@ def add_equipment(self, set_of_equip: Set[str]) -> None: ) # from the set of equip item names create a set of item codes # this function is calling parameters from this - equip_codes = set( - self.get_equip_item_code_from_item_name( - self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'], - item_name - ) for item_name in set_of_equip - ) + equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) self.EQUIPMENT.update(equip_codes) def initialise(self): From a7426b10d56ef710b7822c4e1937636f00c159bf Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 21 Jan 2024 21:58:17 +0000 Subject: [PATCH 302/443] equip_catalogue: add item codes to catalogue by requested details (1 item per row) --- .../equipment/equipment_catalogue.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index aa4552e536..4dcd533903 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -125,14 +125,20 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): def details_col_to_str(details_col): return details_col.apply(lambda x: ', '.join(map(str, x))) - def get_equip_item_name_from_item_code(lookup_df: pd.DataFrame, equip_item_code: str) -> int: - """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" + def get_equip_item_name_from_item_code(equip_item_code: int) -> str: + """Helper function to provide the equip item name (a string) when provided with the equip_item_code (an int).""" + lookup_df = equip_resource_items_pkgs_df return str(pd.unique(lookup_df.loc[lookup_df["Equip_Code"] == equip_item_code, "Equip_Item"])[0]) + def get_equip_item_code_from_item_name(equip_item_name: str) -> int: + """Helper function to provide the equip item code (an int) when provided with the equip_item_name (a string)""" + lookup_df = equip_resource_items_pkgs_df + return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) + def lists_of_equip_item_codes_to_strings_of_list_of_equip_item_names(list_of_equip_item_codes_col): return list_of_equip_item_codes_col.apply( lambda x: - str(sorted([get_equip_item_name_from_item_code(equip_resource_items_pkgs_df, item_code) for item_code in x])) + str(sorted([get_equip_item_name_from_item_code(item_code) for item_code in x])) ) def strings_of_list_to_lists_of_strings(strings_of_list_col): @@ -221,7 +227,10 @@ def strings_of_list_to_lists_of_strings(strings_of_list_col): equipment_counts_by_time_and_requested_details['equipment'] ) exploded_df = equipment_counts_by_time_and_requested_details.explode('equipment') - exploded_df = exploded_df.set_index(['equipment'], append=True) + # Add column with equip item code + exploded_df['equip_code'] = exploded_df['equipment'].apply(lambda x: get_equip_item_code_from_item_name(x)) + exploded_df = exploded_df.set_index(['equipment', 'equip_code'], append=True) + # Sum values with the same multi-index exploded_df = exploded_df.groupby(level=exploded_df.index.names).sum() From fc6bea253824a8a1d2c9b4a24e23aea1ccdb6efb Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 21 Jan 2024 18:47:29 +0000 Subject: [PATCH 303/443] hs: allow adding equip by pkg name(s) --- src/tlo/methods/healthsystem.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index c83854d06a..b59a509d12 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -348,6 +348,12 @@ def get_equip_item_code_from_item_name(self, equip_item_name: str) -> int: lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'] return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) + def get_equip_item_codes_from_pkg_name(self, equip_pkg_name: str) -> Set[int]: + """Helper function to provide the equip_item_codes (a set of ints) when provided with the equip_pkg_name of the + equipment package""" + lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'] + return set(lookup_df.loc[lookup_df["Equip_Pkg"] == equip_pkg_name, "Equip_Code"]) + def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: """Helper function to set essential equipment. @@ -378,11 +384,26 @@ def add_equipment(self, set_of_equip: Set[str]) -> None: "Argument to add_equipment should be a non-empty set of strings of " "equipment item names from ResourceFile_Equipment.csv." ) - # from the set of equip item names create a set of item codes - # this function is calling parameters from this + # from the set of equip item names create a set of equip item codes equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) self.EQUIPMENT.update(equip_codes) + def add_equipment_from_pkg(self, set_of_pkgs: Set[str]) -> None: + """Helper function to update equipment with equipment from pkg(s). + + Should be passed a set of equipment pkgs names (strings). + """ + # Update EQUIPMENT if the given set_of_pkgs in correct format, ie a non-empty set of strings + if not isinstance(set_of_pkgs, set) or any(not isinstance(item, str) for item in set_of_pkgs) or \ + (set_of_pkgs in [set(), None, {''}]): + raise ValueError( + "Argument to add_equipment_from_pkg should be a non-empty set of strings of " + "equipment pkg names from ResourceFile_Equipment.csv." + ) + # update EQUIPMENT with eqip item codes from equip pkgs with provided names + for pkg_name in set_of_pkgs: + self.EQUIPMENT.update(self.get_equip_item_codes_from_pkg_name(pkg_name)) + def initialise(self): """Initialise the HSI: * Set the facility_info From 1c987c3afb5e828adf5725baf7b7897c9e5e8709 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 21 Jan 2024 19:04:31 +0000 Subject: [PATCH 304/443] co & RF_Equip: an example of usage of equipment pkg --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- src/tlo/methods/contraception.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index f0a2b53c9f..90012472cb 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd2c3558a4d30dfb6d913054513e1d6df1b91f4955b60371e762eff05ac99afd -size 32759 +oid sha256:8b5c09ef0800c69ed916edab8bf469ce83c47a09e1e75c7263a44c24f692ed3f +size 33197 diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 6fcae5817b..a38120d024 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1292,6 +1292,11 @@ def apply(self, person_id, squeeze_factor): self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' }) + self.add_equipment_from_pkg({ + 'Minor Surgery' + }) + # TODO: this is just an example - update once figured out what we want in the pkgs + # (! Update also the RF_Equipment accordingly !) elif _new_contraceptive == 'IUD': self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' From 38ff44c9dde85035a3e529ee28dd297dceba3c8e Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 24 Jan 2024 15:32:31 +0000 Subject: [PATCH 305/443] utils: use pandas fnc (instead of make one) --- src/tlo/analysis/utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index e2d5db608d..d1ca9b730c 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -18,6 +18,7 @@ import numpy as np import pandas as pd import squarify +from pandas.api.types import is_numeric_dtype from tlo import Date, Simulation, logging, util from tlo.logging.reader import LogData @@ -282,9 +283,6 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series: # get number of draws and numbers of runs info = get_scenario_info(results_folder) - def is_number(element): - return isinstance(element, (int, float)) - # Collect results from each draw/run res = dict() for draw in range(info['number_of_draws']): @@ -296,7 +294,7 @@ def is_number(element): df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key] output_from_eval: pd.Series = generate_series(df) assert pd.Series == type(output_from_eval), 'Custom command does not generate a pd.Series' - if output_from_eval.apply(is_number).all(): + if is_numeric_dtype(output_from_eval): res[draw_run] = output_from_eval * get_multiplier(draw, run) else: res[draw_run] = output_from_eval From e0132f8e02bf68b7c75ee6dfe1a6311f28bd8abe Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 24 Jan 2024 22:47:41 +0000 Subject: [PATCH 306/443] hs: ESS_EQUIP as HSI_Event's attribute; if settings of ESS_EQUIP forgotten -> set to empty & warn --- src/tlo/methods/healthsystem.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index b59a509d12..f57367557a 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -183,12 +183,14 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + self.ESSENTIAL_EQUIPMENT = None # self.set_equipment_essential_to_run_event({''}) # HSI needs this attribute, but it is not defined in the Base # class to allow verification of its existence as a test for # each HSI event, showing that equipment setup was thought # through for the event. self.EQUIPMENT = set() - + self._hsi_event_names_missing_ess_equip = set() # The names of hsi events for which the settings of + # essential equipment is missing. @property def bed_days_allocated_to_this_event(self): if self._received_info_about_bed_days is None: @@ -435,6 +437,11 @@ def initialise(self): # Do checks _ = self._check_if_appt_footprint_can_run() + # Set essential equip to empty set if not exists and warn about missing settings + if self.ESSENTIAL_EQUIPMENT is None: + self.set_equipment_essential_to_run_event({''}) + self._hsi_event_names_missing_ess_equip.update(self.__class__.__name__) + def _check_if_appt_footprint_can_run(self): """Check that event (if individual level) is able to run with this configuration of officers (i.e. check that this does not demand officers that are _never_ available), and issue warning if not.""" @@ -469,6 +476,18 @@ def as_namedtuple( equipment=(tuple(sorted(self.EQUIPMENT))) ) + def on_simulation_end(self): + """Do tasks at the end of the simulation: Raise warning and enter to log the set of hsi event names which were + initialised but the settings of essential equipment is missing.""" + if self._hsi_event_names_missing_ess_equip: + warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential" + f"equipment is missing:/n" + f"{self._hsi_event_names_missing_ess_equip}")) + logger.info( + key="hsi_event_names_missing_ess_equip", + data={"event_names": self._hsi_event_names_missing_ess_equip} + ) + class HSIEventWrapper(Event): """This is wrapper that contains an HSI event. From 2859530208e57d9be1644122a6e7ae8cde53443a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 24 Jan 2024 23:58:52 +0000 Subject: [PATCH 307/443] hs: fixed saving _hsi_event_names_missing_ess_equip --- src/tlo/methods/healthsystem.py | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index f57367557a..9301afde92 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -189,8 +189,7 @@ def __init__(self, module, *args, **kwargs): # each HSI event, showing that equipment setup was thought # through for the event. self.EQUIPMENT = set() - self._hsi_event_names_missing_ess_equip = set() # The names of hsi events for which the settings of - # essential equipment is missing. + @property def bed_days_allocated_to_this_event(self): if self._received_info_about_bed_days is None: @@ -440,7 +439,7 @@ def initialise(self): # Set essential equip to empty set if not exists and warn about missing settings if self.ESSENTIAL_EQUIPMENT is None: self.set_equipment_essential_to_run_event({''}) - self._hsi_event_names_missing_ess_equip.update(self.__class__.__name__) + self.sim.modules['HealthSystem']._hsi_event_names_missing_ess_equip.update(self.__class__.__name__) def _check_if_appt_footprint_can_run(self): """Check that event (if individual level) is able to run with this configuration of officers (i.e. check that @@ -476,19 +475,6 @@ def as_namedtuple( equipment=(tuple(sorted(self.EQUIPMENT))) ) - def on_simulation_end(self): - """Do tasks at the end of the simulation: Raise warning and enter to log the set of hsi event names which were - initialised but the settings of essential equipment is missing.""" - if self._hsi_event_names_missing_ess_equip: - warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential" - f"equipment is missing:/n" - f"{self._hsi_event_names_missing_ess_equip}")) - logger.info( - key="hsi_event_names_missing_ess_equip", - data={"event_names": self._hsi_event_names_missing_ess_equip} - ) - - class HSIEventWrapper(Event): """This is wrapper that contains an HSI event. @@ -835,6 +821,9 @@ def __init__( "'year', 'simulation' or None." ) + self._hsi_event_names_missing_ess_equip = set() # The names of HSI events for which the settings of essential + # equipment is missing. + def read_parameters(self, data_folder): path_to_resourcefiles_for_healthsystem = Path(self.resourcefilepath) / 'healthsystem' @@ -974,7 +963,9 @@ def on_birth(self, mother_id, child_id): self.bed_days.on_birth(self.sim.population.props, mother_id, child_id) def on_simulation_end(self): - """Put out to the log the information from the tracker of the last day of the simulation""" + """Put out to the log the information from the tracker of the last day of the simulation. + Raise warning and enter to log the set of hsi event names which were initialised but the settings of essential + equipment is missing.""" self.bed_days.on_simulation_end() self.consumables.on_simulation_end() if self._hsi_event_count_log_period == "simulation": @@ -1000,6 +991,15 @@ def on_simulation_end(self): } ) + if self._hsi_event_names_missing_ess_equip: + warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential" + f"equipment is missing:/n" + f"{self._hsi_event_names_missing_ess_equip}")) + logger_summary.info( + key="hsi_event_names_missing_ess_equip", + data={"event_names": self._hsi_event_names_missing_ess_equip} + ) + def setup_priority_policy(self): # Determine name of policy to be considered **at the start of the simulation**. From e5df9500907f7c974ae1099b01e4637fede947c7 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 29 Jan 2024 17:36:24 +0000 Subject: [PATCH 308/443] hs: fixed updating _hsi_event_names_missing_ess_equip --- src/tlo/methods/healthsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 9301afde92..eee6d6c4ea 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -439,7 +439,7 @@ def initialise(self): # Set essential equip to empty set if not exists and warn about missing settings if self.ESSENTIAL_EQUIPMENT is None: self.set_equipment_essential_to_run_event({''}) - self.sim.modules['HealthSystem']._hsi_event_names_missing_ess_equip.update(self.__class__.__name__) + self.sim.modules['HealthSystem']._hsi_event_names_missing_ess_equip.update({self.__class__.__name__}) def _check_if_appt_footprint_can_run(self): """Check that event (if individual level) is able to run with this configuration of officers (i.e. check that @@ -992,7 +992,7 @@ def on_simulation_end(self): ) if self._hsi_event_names_missing_ess_equip: - warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential" + warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential " f"equipment is missing:/n" f"{self._hsi_event_names_missing_ess_equip}")) logger_summary.info( From 5c663280550196bebe68a1c2104167648f02e8f5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 29 Jan 2024 18:36:20 +0000 Subject: [PATCH 309/443] hs: TODO smt odd going on with hsi_event_names_missing_ess_equip warning --- src/tlo/methods/healthsystem.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index eee6d6c4ea..e997443ed3 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -999,6 +999,10 @@ def on_simulation_end(self): key="hsi_event_names_missing_ess_equip", data={"event_names": self._hsi_event_names_missing_ess_equip} ) + # TODO: smt odd is going on, some hsi events were logged, according to my equipment_catalogue script, + # for which the essential equipment is not define, but they are not included in this warning. + # E.g. HSI_BladderCancer_Investigation_Following_Blood_Urine, HSI_BladderCancer_StartTreatment, + # HSI_BreastCancer_Investigation_Following_breast_lump_discernible, ... def setup_priority_policy(self): From 8daa621869e2d2c9d8511852ec933ca9923d915a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 29 Jan 2024 18:37:10 +0000 Subject: [PATCH 310/443] hs: sort hsi_event_names_missing_ess_equip warning --- src/tlo/methods/healthsystem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index e997443ed3..7a1136d6d5 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -992,12 +992,13 @@ def on_simulation_end(self): ) if self._hsi_event_names_missing_ess_equip: + hsi_event_names_missing_ess_equip = sorted(self._hsi_event_names_missing_ess_equip) warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential " f"equipment is missing:/n" - f"{self._hsi_event_names_missing_ess_equip}")) + f"{hsi_event_names_missing_ess_equip}")) logger_summary.info( key="hsi_event_names_missing_ess_equip", - data={"event_names": self._hsi_event_names_missing_ess_equip} + data={"event_names": hsi_event_names_missing_ess_equip} ) # TODO: smt odd is going on, some hsi events were logged, according to my equipment_catalogue script, # for which the essential equipment is not define, but they are not included in this warning. From 1a69dca8ef58d014a1ae90b8312a3bd143e75d85 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 29 Jan 2024 18:38:06 +0000 Subject: [PATCH 311/443] hs: equip_item_and_package_code_lookups renamed to equip_item_and_package_lookups --- src/tlo/methods/healthsystem.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 7a1136d6d5..1433b0fe93 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -346,13 +346,13 @@ def make_appt_footprint(self, dict_of_appts): def get_equip_item_code_from_item_name(self, equip_item_name: str) -> int: """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" - lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'] + lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) def get_equip_item_codes_from_pkg_name(self, equip_pkg_name: str) -> Set[int]: """Helper function to provide the equip_item_codes (a set of ints) when provided with the equip_pkg_name of the equipment package""" - lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'] + lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] return set(lookup_df.loc[lookup_df["Equip_Pkg"] == equip_pkg_name, "Equip_Code"]) def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: @@ -597,7 +597,7 @@ class HealthSystem(Module): "Availability of beds. If 'default' then use the availability specified in the ResourceFile; if " "'none', then let no beds be ever be available; if 'all', then all beds are always available. NB. This " "parameter is over-ridden if an argument is provided to the module initialiser."), - 'equip_item_and_package_code_lookups': Parameter( + 'equip_item_and_package_lookups': Parameter( Types.DATA_FRAME, "Items based on the the HSSP III 1K Equipment Costing (SEL Costing Sheet): " "https://www.health.gov.mw/download/hssp-iii/, packages created in consultation with clinicians."), @@ -868,7 +868,7 @@ def read_parameters(self, data_folder): path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Bed_Capacity.csv') # Read in ResourceFile_Equipment - self.parameters['equip_item_and_package_code_lookups'] = pd.read_csv( + self.parameters['equip_item_and_package_lookups'] = pd.read_csv( path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Equipment.csv') # Data on the priority of each Treatment_ID that should be adopted in the queueing system according to different From 42492fa6ee222558c72c52e0da1a0391ff3047c8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 29 Jan 2024 22:07:24 +0000 Subject: [PATCH 312/443] hs: ignore_unknown_equip_names --- src/tlo/methods/healthsystem.py | 116 +++++++++++++++++++++++++++----- 1 file changed, 98 insertions(+), 18 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1433b0fe93..3013c8a5f9 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -355,6 +355,45 @@ def get_equip_item_codes_from_pkg_name(self, equip_pkg_name: str) -> Set[int]: lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] return set(lookup_df.loc[lookup_df["Equip_Pkg"] == equip_pkg_name, "Equip_Code"]) + def ignore_unknown_equip_names(self, set_of_names: Set[str], type_in_set: str) -> Set[str]: + """Helper function to check if the equipment item or pkg names (depending on type_in_set: 'item' or 'pkg') from + the provided set are in the RF_Equipment. If they are not, they are added to a set to be warned about at the end + of the simulation. + + Only known (item or pkg) names are returned.""" + if set_of_names in [set(), None, {''}]: + return set() + + def add_unknown_names_to_dict(unknown_names_to_add: Set[str], dict_to_be_added_to: Dict) -> Dict: + if self.__class__.__name__ not in dict_to_be_added_to.keys(): + dict_to_be_added_to.update( + {self.__class__.__name__: unknown_names_to_add} + ) + else: + dict_to_be_added_to[self.__class__.__name__].update( + unknown_names_to_add + ) + return dict_to_be_added_to + + lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] + if type_in_set == "item": + unknown_names = set_of_names.difference(set(lookup_df["Equip_Item"])) + if unknown_names: + self.sim.modules['HealthSystem']._equip_items_missing_in_RF = \ + add_unknown_names_to_dict( + unknown_names, self.sim.modules['HealthSystem']._equip_items_missing_in_RF + ) + + elif type_in_set == "pkg": + unknown_names = set_of_names.difference(set(lookup_df["Equip_Pkg"])) + if unknown_names: + self.sim.modules['HealthSystem']._equip_pkgs_missing_in_RF = \ + add_unknown_names_to_dict( + unknown_names, self.sim.modules['HealthSystem']._equip_pkgs_missing_in_RF + ) + + return set_of_names.difference(unknown_names) + def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: """Helper function to set essential equipment. @@ -367,7 +406,8 @@ def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: "equipment item names from ResourceFile_Equipment.csv." ) - if set_of_equip not in [set(), None, {''}]: + set_of_equip = self.ignore_unknown_equip_names(set_of_equip, "item") + if set_of_equip: equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) self.ESSENTIAL_EQUIPMENT = equip_codes else: @@ -385,9 +425,12 @@ def add_equipment(self, set_of_equip: Set[str]) -> None: "Argument to add_equipment should be a non-empty set of strings of " "equipment item names from ResourceFile_Equipment.csv." ) - # from the set of equip item names create a set of equip item codes - equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) - self.EQUIPMENT.update(equip_codes) + # from the set of equip item names create a set of equip item codes, ignore unknown equip names + # (ie not included in RF_Equipment) + set_of_equip = self.ignore_unknown_equip_names(set_of_equip, "item") + if set_of_equip: + equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) + self.EQUIPMENT.update(equip_codes) def add_equipment_from_pkg(self, set_of_pkgs: Set[str]) -> None: """Helper function to update equipment with equipment from pkg(s). @@ -401,9 +444,12 @@ def add_equipment_from_pkg(self, set_of_pkgs: Set[str]) -> None: "Argument to add_equipment_from_pkg should be a non-empty set of strings of " "equipment pkg names from ResourceFile_Equipment.csv." ) - # update EQUIPMENT with eqip item codes from equip pkgs with provided names - for pkg_name in set_of_pkgs: - self.EQUIPMENT.update(self.get_equip_item_codes_from_pkg_name(pkg_name)) + # update EQUIPMENT with eqip item codes from equip pkgs with provided names, ignore unknown equip names + # (ie not included in RF_Equipment) + set_of_pkgs = self.ignore_unknown_equip_names(set_of_pkgs, "pkg") + if set_of_pkgs: + for pkg_name in set_of_pkgs: + self.EQUIPMENT.update(self.get_equip_item_codes_from_pkg_name(pkg_name)) def initialise(self): """Initialise the HSI: @@ -823,6 +869,10 @@ def __init__( self._hsi_event_names_missing_ess_equip = set() # The names of HSI events for which the settings of essential # equipment is missing. + self._equip_items_missing_in_RF = dict() # The equipment item names called for an HSI event, but are missing in + # the RF_Equipment. + self._equip_pkgs_missing_in_RF = dict() # The equipment pkg names called for an HSI event, but are missing in + # the RF_Equipment. def read_parameters(self, data_folder): @@ -991,19 +1041,49 @@ def on_simulation_end(self): } ) - if self._hsi_event_names_missing_ess_equip: - hsi_event_names_missing_ess_equip = sorted(self._hsi_event_names_missing_ess_equip) - warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential " - f"equipment is missing:/n" - f"{hsi_event_names_missing_ess_equip}")) + if self._hsi_event_names_missing_ess_equip: + hsi_event_names_missing_ess_equip = sorted(self._hsi_event_names_missing_ess_equip) + warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential " + f"equipment is missing:/n" + f"{hsi_event_names_missing_ess_equip}")) + logger_summary.info( + key="hsi_event_names_missing_ess_equip", + data={"event_names": hsi_event_names_missing_ess_equip} + ) + # TODO: smt odd is going on, some hsi events were logged, according to my equipment_catalogue script, + # for which the essential equipment is not define, but they are not included in this warning. + # E.g. HSI_BladderCancer_Investigation_Following_Blood_Urine, HSI_BladderCancer_StartTreatment, + # HSI_BreastCancer_Investigation_Following_breast_lump_discernible, ... + + def sort_dict_for_print(dict_to_sort: Dict) -> Dict: + sorted_list = sorted(dict_to_sort.items()) + sorted_dict = {} + for key, value in sorted_list: + sorted_dict[key] = sorted(value) + return sorted_dict + + if self._equip_items_missing_in_RF: + sorted_equip_items_missing_in_RF = sort_dict_for_print(self._equip_items_missing_in_RF) + warnings.warn(UserWarning(f"The equipment item names called for an HSI event, but missing in the " + f"RF_Equipment:/n" + f"{sorted_equip_items_missing_in_RF}")) + + for _hsi_event_name, _item_names in sorted_equip_items_missing_in_RF.items(): + logger_summary.info( + key="equip_items_missing_in_RF", + data={_hsi_event_name: _item_names} + ) + + if self._equip_pkgs_missing_in_RF: + sorted_equip_pkgs_missing_in_RF = sort_dict_for_print(self._equip_pkgs_missing_in_RF) + warnings.warn(UserWarning(f"The equipment pkg names called for an HSI event, but missing in the " + f"RF_Equipment:/n" + f"{sorted_equip_pkgs_missing_in_RF}")) + for _hsi_event_name, _pkg_names in sorted_equip_pkgs_missing_in_RF.items(): logger_summary.info( - key="hsi_event_names_missing_ess_equip", - data={"event_names": hsi_event_names_missing_ess_equip} + key="equip_pkgs_missing_in_RF", + data={_hsi_event_name: _pkg_names} ) - # TODO: smt odd is going on, some hsi events were logged, according to my equipment_catalogue script, - # for which the essential equipment is not define, but they are not included in this warning. - # E.g. HSI_BladderCancer_Investigation_Following_Blood_Urine, HSI_BladderCancer_StartTreatment, - # HSI_BreastCancer_Investigation_Following_breast_lump_discernible, ... def setup_priority_policy(self): From d0d2828dc3510a88180e485b45d13c9cbd4180a5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 30 Jan 2024 16:35:15 +0000 Subject: [PATCH 313/443] hs: warning messages shortened --- src/tlo/methods/healthsystem.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 3013c8a5f9..ffe64524cc 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1043,8 +1043,7 @@ def on_simulation_end(self): if self._hsi_event_names_missing_ess_equip: hsi_event_names_missing_ess_equip = sorted(self._hsi_event_names_missing_ess_equip) - warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential " - f"equipment is missing:/n" + warnings.warn(UserWarning(f"Missing settings of essential equipment for HSI events:/n" f"{hsi_event_names_missing_ess_equip}")) logger_summary.info( key="hsi_event_names_missing_ess_equip", @@ -1064,8 +1063,7 @@ def sort_dict_for_print(dict_to_sort: Dict) -> Dict: if self._equip_items_missing_in_RF: sorted_equip_items_missing_in_RF = sort_dict_for_print(self._equip_items_missing_in_RF) - warnings.warn(UserWarning(f"The equipment item names called for an HSI event, but missing in the " - f"RF_Equipment:/n" + warnings.warn(UserWarning(f"Equipment item names were not recognised:/n" f"{sorted_equip_items_missing_in_RF}")) for _hsi_event_name, _item_names in sorted_equip_items_missing_in_RF.items(): @@ -1076,8 +1074,7 @@ def sort_dict_for_print(dict_to_sort: Dict) -> Dict: if self._equip_pkgs_missing_in_RF: sorted_equip_pkgs_missing_in_RF = sort_dict_for_print(self._equip_pkgs_missing_in_RF) - warnings.warn(UserWarning(f"The equipment pkg names called for an HSI event, but missing in the " - f"RF_Equipment:/n" + warnings.warn(UserWarning(f"Equipment pkg names were not recognised:/n" f"{sorted_equip_pkgs_missing_in_RF}")) for _hsi_event_name, _pkg_names in sorted_equip_pkgs_missing_in_RF.items(): logger_summary.info( From d1c752361f8966c9dfebf136eb9f5d4aee9ef707 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 30 Jan 2024 16:59:04 +0000 Subject: [PATCH 314/443] tox: pytest-version-hotfix / TODO: revert once #1264 resolved --- tox.ini | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 94949bd6d8..9a16e7a1c6 100644 --- a/tox.ini +++ b/tox.ini @@ -27,7 +27,9 @@ passenv = usedevelop = false deps = -r{toxinidir}/requirements/base.txt - pytest + ; Pin pytest version as some tests use features deprecated in v8 + ; See https://github.com/UCL/TLOmodel/issues/1264 + pytest==7.4.4 pytest-cov commands = {posargs:pytest --cov --cov-report=term-missing -vv tests} @@ -38,7 +40,9 @@ deps = pandas15: pandas==1.5.3 pandas20: pandas==2.0.0 pandas21: pandas==2.1.0 - pytest + ; Pin pytest version as some tests use features deprecated in v8 + ; See https://github.com/UCL/TLOmodel/issues/1264 + pytest==7.4.4 pytest-cov [testenv:spell] From c3175406944c21bc04cd83a037880e909910c59a Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 1 Feb 2024 09:00:11 +0000 Subject: [PATCH 315/443] removed urine dipstick equipment (only consumable) --- src/tlo/methods/care_of_women_during_pregnancy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 369be33383..03ed3271de 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -733,7 +733,6 @@ def screening_interventions_delivered_at_every_contact(self, hsi_event): # Delivery of the intervention is conditioned on a random draw against a probability that the intervention # would be delivered (used to calibrate to SPA data - acts as proxy for clinical quality) if self.rng.random_sample() < params['prob_intervention_delivered_urine_ds']: - hsi_event.EQUIPMENT.update({'Urine dip Stick'}) # check consumables avail = pregnancy_helper_functions.return_cons_avail( From e8bb6b76971a470de7ea810aeb2c54284c5fb9db Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 1 Feb 2024 09:27:46 +0000 Subject: [PATCH 316/443] removed comments. updated indication for equipment with antibiotic provision --- src/tlo/methods/alri.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index bdf930216b..b78c520a69 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2597,8 +2597,8 @@ def _get_disease_classification_for_treatment_decision(self, 'chest_indrawing_pneumonia', (symptoms-based assessment) 'cough_or_cold' (symptoms-based assessment) }.""" - # TODO: Currently this is logged as equipment even if pulse ox consumable isnt available - self.EQUIPMENT.update({'Pulse oximeter'}) + if use_oximeter: + self.EQUIPMENT.update({'Pulse oximeter'}) child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) @@ -2654,14 +2654,13 @@ def _try_treatment(antibiotic_indicated: Tuple[str], oxygen_indicated: bool) -> oxygen_available = self._get_cons('Oxygen_Therapy') oxygen_provided = (oxygen_available and oxygen_indicated) - # todo: should equipment only be logged if consumables are available? # If individual requires oxygen, update equipment if oxygen_provided: self.EQUIPMENT.update({'Oxygen cylinder, with regulator', 'Nasal Prongs'}) # If individual requires intravenous antibiotics, update equipment - if antibiotic_available in ('1st_line_IV_antibiotics', - 'Benzylpenicillin_gentamicin_therapy_for_severe_pneumonia'): + if antibiotic_provided in ('1st_line_IV_antibiotics', + 'Benzylpenicillin_gentamicin_therapy_for_severe_pneumonia'): self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) all_things_needed_available = antibiotic_available and ( From 32bb66b45279a14d8afcf4895fe403f183595de0 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 1 Feb 2024 09:34:07 +0000 Subject: [PATCH 317/443] update comments in rti --- src/tlo/methods/rti.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 9a9f5612d8..034f1cfebf 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3737,8 +3737,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 TODO: is this still the case - should this be scheduled/ignored? """ def __init__(self, module, person_id): @@ -5079,8 +5077,7 @@ 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: - # TODO: not all surgeries conducted here are laparotomies however likely to have most appropriate equipment - # so should be fine for now + # TODO: link to surgical equipment package when that exists self.EQUIPMENT.update({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) # create a dictionary to store the recovery times for each injury in days From 6ace152be36e97ff751f54f48a8d6c412c7c32c3 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 1 Feb 2024 10:47:52 +0000 Subject: [PATCH 318/443] added endoscope and ECG to equipment resource file --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index f0a2b53c9f..aaab6001c4 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd2c3558a4d30dfb6d913054513e1d6df1b91f4955b60371e762eff05ac99afd -size 32759 +oid sha256:28be2f79f7f5f838cd6b3308749ef83eac44af90b6e46bde880985f6f328145d +size 33202 From 14ab62f2cc62cd85952f87c0c91a01f33b1664a1 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 1 Feb 2024 10:59:11 +0000 Subject: [PATCH 319/443] added endoscope equipment call to oesophageal_cancer.py --- src/tlo/methods/oesophagealcancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index 8ec51f6ce7..35ce1bb34b 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -676,7 +676,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available update equipment and run the dx_test representing the biopsy # n.b. endoscope not in equipment list - self.EQUIPMENT.update({'Ordinary Microscope'}) + self.EQUIPMENT.update({'Endoscope', 'Ordinary Microscope'}) # Use an endoscope to diagnose whether the person has Oesophageal Cancer: dx_result = hs.dx_manager.run_dx_test( From 41646c87947576e40e0ce232c292b61e5b823585 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 1 Feb 2024 11:33:24 +0000 Subject: [PATCH 320/443] added ECG equipment to NCDs --- src/tlo/methods/cardio_metabolic_disorders.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index 66ed6cbd0b..2ae89ca40f 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1458,7 +1458,7 @@ def do_for_each_condition(self, _c) -> bool: return if _c == 'chronic_ischemic_heart_disease': - self.EQUIPMENT.update({'Stethoscope'}) + self.EQUIPMENT.update({'Electrocardiogram', 'Stethoscope'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run=f'assess_{_c}', @@ -1710,6 +1710,9 @@ def do_for_each_event_to_be_investigated(self, _ev): if _ev == 'ever_stroke': self.EQUIPMENT.update({'Computed Tomography (CT machine)', 'CT scanner accessories'}) + if _ev == 'ever_heart_attack': + self.EQUIPMENT.update({'Electrocardiogram'}) + dx_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run=f'assess_{_ev}', hsi_event=self From 5806bde248ab932a0c568090cdba3f86f8149e33 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 5 Feb 2024 18:43:05 +0000 Subject: [PATCH 321/443] hs: update comment --- src/tlo/methods/healthsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index f33202cca8..9814ffa6a1 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1015,8 +1015,8 @@ def on_birth(self, mother_id, child_id): def on_simulation_end(self): """Put out to the log the information from the tracker of the last day of the simulation. - Raise warning and enter to log the set of hsi event names which were initialised but the settings of essential - equipment is missing.""" + Raise warning and enter to log if any hsi event initialised but the settings of essential + equipment is missing, if any equipment item or package requested but not recognised.""" self.bed_days.on_simulation_end() self.consumables.on_simulation_end() if self._hsi_event_count_log_period == "simulation": From f1f759ef81d951648fd6847bacf370f393f9557c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 5 Feb 2024 19:00:37 +0000 Subject: [PATCH 322/443] modules: use v2 of equip structure --- src/tlo/methods/alri.py | 10 +-- src/tlo/methods/bladder_cancer.py | 18 ++--- src/tlo/methods/breast_cancer.py | 16 ++--- src/tlo/methods/cardio_metabolic_disorders.py | 26 +++---- .../methods/care_of_women_during_pregnancy.py | 68 +++++++++---------- src/tlo/methods/contraception.py | 8 +-- src/tlo/methods/copd.py | 4 +- src/tlo/methods/depression.py | 6 +- src/tlo/methods/diarrhoea.py | 6 +- src/tlo/methods/epi.py | 2 +- src/tlo/methods/epilepsy.py | 4 +- src/tlo/methods/hiv.py | 12 ++-- src/tlo/methods/labour.py | 26 +++---- src/tlo/methods/malaria.py | 12 ++-- src/tlo/methods/measles.py | 4 +- src/tlo/methods/newborn_outcomes.py | 8 +-- src/tlo/methods/oesophagealcancer.py | 14 ++-- src/tlo/methods/other_adult_cancers.py | 14 ++-- src/tlo/methods/postnatal_supervisor.py | 2 +- src/tlo/methods/prostate_cancer.py | 18 ++--- src/tlo/methods/rti.py | 38 +++++------ src/tlo/methods/schisto.py | 8 +-- src/tlo/methods/stunting.py | 2 +- src/tlo/methods/tb.py | 28 ++++---- 24 files changed, 177 insertions(+), 177 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index b78c520a69..7c80223763 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2290,7 +2290,7 @@ def __init__(self, module: Module, person_id: int, facility_level: str = "0", in self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) self.is_followup_following_treatment_failure = is_followup_following_treatment_failure @@ -2598,7 +2598,7 @@ def _get_disease_classification_for_treatment_decision(self, 'cough_or_cold' (symptoms-based assessment) }.""" if use_oximeter: - self.EQUIPMENT.update({'Pulse oximeter'}) + self.add_equipment({'Pulse oximeter'}) child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) @@ -2656,12 +2656,12 @@ def _try_treatment(antibiotic_indicated: Tuple[str], oxygen_indicated: bool) -> # If individual requires oxygen, update equipment if oxygen_provided: - self.EQUIPMENT.update({'Oxygen cylinder, with regulator', 'Nasal Prongs'}) + self.add_equipment({'Oxygen cylinder, with regulator', 'Nasal Prongs'}) # If individual requires intravenous antibiotics, update equipment if antibiotic_provided in ('1st_line_IV_antibiotics', 'Benzylpenicillin_gentamicin_therapy_for_severe_pneumonia'): - self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + self.add_equipment({'Infusion pump', 'Drip stand'}) all_things_needed_available = antibiotic_available and ( (oxygen_available and oxygen_indicated) or (not oxygen_indicated) @@ -2757,7 +2757,7 @@ def do_on_follow_up_following_treatment_failure(self): _ = self._get_cons('Ceftriaxone_therapy_for_severe_pneumonia') if _: - self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + 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 ab573a5972..cbe592e916 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -661,7 +661,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # equipment: (ultrsound guided) biopsy, lab equipment for histology @@ -689,7 +689,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # Use a biopsy to diagnose whether the person has bladder Cancer # If consumables are available update the use of equipment and run the dx_test representing the biopsy - self.EQUIPMENT.update({'Cystoscope', 'Ordinary Microscope'}) + self.add_equipment({'Cystoscope', 'Ordinary Microscope'}) # Use a cystoscope to diagnose whether the person has bladder Cancer: dx_result = hs.dx_manager.run_dx_test( @@ -738,7 +738,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # equipment: (ultrsound guided) biopsy, lab equipment for histology @@ -766,7 +766,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # Use a biopsy to diagnose whether the person has bladder Cancer # If consumables are available log the use of equipment and run the dx_test representing the biopsy - self.EQUIPMENT.update({'Cystoscope', 'Ordinary Microscope'}) + self.add_equipment({'Cystoscope', 'Ordinary Microscope'}) # Use a cystoscope to diagnose whether the person has bladder Cancer: dx_result = hs.dx_manager.run_dx_test( @@ -820,7 +820,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # equipment: standard equipment for surgery @@ -860,7 +860,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available and the treatment will go ahead - update the equipment # TODO: link to surgical equipment package when that exists - self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', + self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) # Record date and stage of starting treatment @@ -893,7 +893,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # I assume ultrasound (Ultrasound scanning machine) and biopsy @@ -952,7 +952,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # no equipment as far as I am aware @@ -973,7 +973,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment - self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + 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 f485aa2ab3..e534441cc8 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -650,7 +650,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # TODO: Eva's dummy equipment example (not sure if it actually needs to be added and if it is in the RF) # {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} @@ -688,7 +688,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # Use a biopsy to diagnose whether the person has breast Cancer # If consumables are available update the use of equipment and run the dx_test representing the biopsy - self.EQUIPMENT.update({'Ultrasound scanning machine', 'Ordinary Microscope'}) + 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', @@ -747,7 +747,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # ap_oct23 - I believe this will almost always be mastectomy surgery with chemotherapy, so I think for equipment # we just need the standard surgery equipment list. We may need to add radiotherapy when more available. @@ -792,7 +792,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment # TODO: link to surgical equipment package when that exists - self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', + self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) # Log the use of adjuvant chemotherapy @@ -806,7 +806,7 @@ def apply(self, person_id, squeeze_factor): # TODO: Eva's dummy equipment example - it needs to be replaced by real items from the RF # Update equipment - # self.EQUIPMENT.update({'Anything used for mastectomy'}) + # self.add_equipment({'Anything used for mastectomy'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( @@ -834,7 +834,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # ap_oct23 - Eva, I'm not aware of any equipment needed here. Clinical guidelines do not specify what # checks or monitoring are indicated @@ -894,7 +894,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # not sure there is any need for equipment here @@ -915,7 +915,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment - self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + 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/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index 2ae89ca40f..1afb15dd21 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1391,7 +1391,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "CardioMetabolicDisorders_Prevention_CommunityTestingForHypertension" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1403,7 +1403,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.EQUIPMENT.update({'Blood pressure machine'}) + self.add_equipment({'Blood pressure machine'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='assess_hypertension', hsi_event=self @@ -1444,7 +1444,7 @@ def __init__(self, module, person_id, conditions_to_investigate: List, has_any_c self.ACCEPTED_FACILITY_LEVEL = '1b' self.conditions_to_investigate = conditions_to_investigate self.has_any_cmd_symptom = has_any_cmd_symptom - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def do_for_each_condition(self, _c) -> bool: """What to do for each condition that will be investigated. Returns `bool` signalling whether a follow-up HSI @@ -1458,7 +1458,7 @@ def do_for_each_condition(self, _c) -> bool: return if _c == 'chronic_ischemic_heart_disease': - self.EQUIPMENT.update({'Electrocardiogram', 'Stethoscope'}) + self.add_equipment({'Electrocardiogram', 'Stethoscope'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run=f'assess_{_c}', @@ -1493,7 +1493,7 @@ def apply(self, person_id, squeeze_factor): # Do test and trigger treatment (if necessary) for each condition: if any(cond in self.conditions_to_investigate for cond in ('diabetes', 'chronic_kidney_disease', 'chronic_ischemic_hd')): - self.EQUIPMENT.update({'Analyser, Haematology', 'Analyser, Combined Chemistry and Electrolytes'}) + 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] @@ -1518,7 +1518,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.EQUIPMENT.update({'Blood pressure machine'}) + self.add_equipment({'Blood pressure machine'}) dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='assess_hypertension', hsi_event=self @@ -1555,7 +1555,7 @@ def __init__(self, module, person_id, condition): self.TREATMENT_ID = 'CardioMetabolicDisorders_Prevention_WeightLoss' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) self.condition = condition @@ -1567,7 +1567,7 @@ 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.EQUIPMENT.update({'Weighing scale'}) + 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: @@ -1626,7 +1626,7 @@ def __init__(self, module, person_id, condition): self.TREATMENT_ID = 'CardioMetabolicDisorders_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) self.condition = condition @@ -1698,7 +1698,7 @@ def __init__(self, module, person_id, events_to_investigate: List): self.TREATMENT_ID = 'CardioMetabolicDisorders_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '2' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) self.events_to_investigate = events_to_investigate def do_for_each_event_to_be_investigated(self, _ev): @@ -1708,10 +1708,10 @@ def do_for_each_event_to_be_investigated(self, _ev): # Run a test to diagnose whether the person has condition: if _ev == 'ever_stroke': - self.EQUIPMENT.update({'Computed Tomography (CT machine)', 'CT scanner accessories'}) + self.add_equipment({'Computed Tomography (CT machine)', 'CT scanner accessories'}) if _ev == 'ever_heart_attack': - self.EQUIPMENT.update({'Electrocardiogram'}) + self.add_equipment({'Electrocardiogram'}) dx_result = self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run=f'assess_{_ev}', @@ -1769,7 +1769,7 @@ def apply(self, person_id, squeeze_factor): data=('This is HSI_CardioMetabolicDisorders_SeeksEmergencyCareAndGetsTreatment: ' f'The squeeze-factor is {squeeze_factor}.'), ) - self.EQUIPMENT.update({'Analyser, Combined Chemistry and Electrolytes', + self.add_equipment({'Analyser, Combined Chemistry and Electrolytes', 'Analyser, Haematology', 'Patient monitor', 'Drip stand', 'Infusion pump', 'Blood pressure machine', diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 03ed3271de..86168748ad 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -749,7 +749,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.EQUIPMENT.update({'Sphygmomanometer'}) + 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): @@ -920,7 +920,7 @@ def point_of_care_hb_testing(self, hsi_event): logger.info(key='anc_interventions', data={'mother': person_id, 'intervention': 'hb_screen'}) hsi_event.get_consumables(item_codes=self.item_codes_preg_consumables['blood_test_cons']) - hsi_event.EQUIPMENT.update({'Haemoglobinometer'}) + hsi_event.add_equipment({'Haemoglobinometer'}) # We run the test through the dx_manager and if a woman has anaemia and its detected she will be admitted # for further care @@ -1077,7 +1077,7 @@ def gdm_screening(self, hsi_event): if avail: # TODO: this actually might be a fasting blood glucose test (not using glucometer) - hsi_event.EQUIPMENT.update({'Glucometer'}) + hsi_event.add_equipment({'Glucometer'}) if self.sim.modules['HealthSystem'].dx_manager.run_dx_test( dx_tests_to_run='blood_test_glucose', hsi_event=hsi_event): @@ -1215,7 +1215,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_cons']) - hsi_event.EQUIPMENT.update({'Analyser, Haematology'}) + 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) @@ -1256,7 +1256,7 @@ 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.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) # If the woman is receiving blood due to anaemia we apply a probability that a transfusion of 2 units # RBCs will correct this woman's severe anaemia @@ -1302,7 +1302,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.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) # We assume women treated with antihypertensives would no longer be severely hypertensive- meaning they # are not at risk of death from severe gestational hypertension in the PregnancySupervisor event @@ -1340,7 +1340,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.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) def antibiotics_for_prom(self, individual_id, hsi_event): """ @@ -1362,7 +1362,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.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) def ectopic_pregnancy_treatment_doesnt_run(self, hsi_event): """ @@ -1439,7 +1439,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AntenatalFirst': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1463,7 +1463,7 @@ def apply(self, person_id, squeeze_factor): # =================================== INTERVENTIONS ==================================================== # Update equipment used during first ANC visit not directly related to interventions - self.EQUIPMENT.update( + self.add_equipment( {'Weighing scale', 'Height Pole (Stadiometer)', 'MUAC tape', 'Measuring tapes', 'Ultrasound, combined 2/4 pole interferential with vacuum and dual frequency 1-3MHZ'}) @@ -1485,7 +1485,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.EQUIPMENT.update({'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1535,7 +1535,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1557,7 +1557,7 @@ def apply(self, person_id, squeeze_factor): # =================================== INTERVENTIONS ==================================================== # Update equipment used during ANC visit not directly related to interventions - self.EQUIPMENT.update( + self.add_equipment( {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' 'Ultrasound, combined 2/4 pole interferential with vacuum and dual frequency 1-3MHZ'}) @@ -1631,7 +1631,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1650,7 +1650,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + self.add_equipment({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) @@ -1711,7 +1711,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1730,7 +1730,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + self.add_equipment({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) @@ -1787,7 +1787,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1806,7 +1806,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - self.EQUIPMENT.update( + self.add_equipment( {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' 'Ultrasound, combined 2/4 pole interferential with vacuum and dual frequency 1-3MHZ'}) @@ -1861,7 +1861,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1882,7 +1882,7 @@ def apply(self, person_id, squeeze_factor): gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) # =================================== INTERVENTIONS ==================================================== - self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + self.add_equipment({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) @@ -1930,7 +1930,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1949,7 +1949,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - self.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + self.add_equipment({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) gest_age_next_contact = self.module.determine_gestational_age_for_next_contact(person_id) @@ -1993,7 +1993,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2010,7 +2010,7 @@ 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.EQUIPMENT.update({'Weighing scale', 'Measuring tapes', + self.add_equipment({'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic'}) self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) @@ -2050,7 +2050,7 @@ def __init__(self, module, person_id, visit_number): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({('AntenatalFirst' if (self.visit_number == 1) else 'ANCSubsequent'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): @@ -2167,7 +2167,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2208,7 +2208,7 @@ def __init__(self, module, person_id): beddays = self.module.calculate_beddays(person_id) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': beddays}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2466,7 +2466,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.ALERT_OTHER_DISEASES = [] - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2516,7 +2516,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.ALERT_OTHER_DISEASES = [] - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2603,7 +2603,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1b' # any hospital? self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 3}) # todo: check with TC - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2630,7 +2630,7 @@ def apply(self, person_id, squeeze_factor): # Update equipment if intervention can happen if baseline_cons and sf_check: - self.EQUIPMENT.update({'D&C set', 'Suction Curettage machine', 'Drip stand', 'Infusion pump'}) + 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 @@ -2695,7 +2695,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 5}) # todo: check with TC - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2713,7 +2713,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.EQUIPMENT.update({'Laparotomy Set'}) + self.add_equipment({'Laparotomy Set'}) # For women who have sought care after they have experienced rupture we use this treatment variable to # reduce risk of death (women who present prior to rupture do not pass through the death event as we assume diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index db2fcc1498..1ed54e72ba 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1194,7 +1194,7 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) @property def EXPECTED_APPT_FOOTPRINT(self): @@ -1231,7 +1231,7 @@ def apply(self, person_id, squeeze_factor): 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.EQUIPMENT.update({'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine'}) + self.add_equipment({'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine'}) # Determine essential and optional items # TODO: we don't distinguish essential X optional for contraception methods yet, will need to update once we do @@ -1287,11 +1287,11 @@ def apply(self, person_id, squeeze_factor): # Update equipment if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({ + self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' }) elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({ + self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' }) diff --git a/src/tlo/methods/copd.py b/src/tlo/methods/copd.py index 5c27791985..3aac5739fe 100644 --- a/src/tlo/methods/copd.py +++ b/src/tlo/methods/copd.py @@ -521,7 +521,7 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = self.all_facility_levels[self.facility_levels_index] self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 2}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """What to do when someone presents for care with an exacerbation. @@ -529,7 +529,7 @@ def apply(self, person_id, squeeze_factor): """ df = self.sim.population.props - self.EQUIPMENT.update({'Oxygen cylinder, with regulator', 'Nasal Prongs', 'Drip stand', 'Infusion pump'}) + self.add_equipment({'Oxygen cylinder, with regulator', 'Nasal Prongs', 'Drip stand', 'Infusion pump'}) if not self.get_consumables(self.module.item_codes['oxygen']): # refer to the next higher facility if the current facility has no oxygen diff --git a/src/tlo/methods/depression.py b/src/tlo/methods/depression.py index 5d4e22ff1c..45b79589bd 100644 --- a/src/tlo/methods/depression.py +++ b/src/tlo/methods/depression.py @@ -786,7 +786,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MentOPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.num_of_sessions_had = 0 # A counter for the number of sessions of talking therapy had - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """Set the property `de_ever_talk_ther` to be True and schedule the next session in the course if the person @@ -819,7 +819,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Depression_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MentOPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -861,7 +861,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Depression_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/diarrhoea.py b/src/tlo/methods/diarrhoea.py index 7b0109b324..03e71a19cb 100644 --- a/src/tlo/methods/diarrhoea.py +++ b/src/tlo/methods/diarrhoea.py @@ -1524,7 +1524,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Diarrhoea_Treatment_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """Run `do_treatment` for this person from an out-potient setting.""" @@ -1549,7 +1549,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 2}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """Run `do_treatment` for this person from an in-potient setting.""" @@ -1558,7 +1558,7 @@ def apply(self, person_id, squeeze_factor): if not df.at[person_id, 'is_alive']: return - self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + self.add_equipment({'Infusion pump', 'Drip stand'}) self.module.do_treatment(person_id=person_id, hsi_event=self) diff --git a/src/tlo/methods/epi.py b/src/tlo/methods/epi.py index 2d5f442752..801f032236 100644 --- a/src/tlo/methods/epi.py +++ b/src/tlo/methods/epi.py @@ -395,7 +395,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi="1a", suppress_ self.TREATMENT_ID = self.treatment_id() self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi - self.EQUIPMENT = set() # no equipment required + self.set_equipment_essential_to_run_event({''}) # no equipment required def treatment_id(self): """subclasses should implement this method to return the TREATMENT_ID""" diff --git a/src/tlo/methods/epilepsy.py b/src/tlo/methods/epilepsy.py index 0ccf714d49..06e450cdb7 100644 --- a/src/tlo/methods/epilepsy.py +++ b/src/tlo/methods/epilepsy.py @@ -590,7 +590,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Epilepsy_Treatment_Start' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -639,7 +639,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self._DEFAULT_APPT_FOOTPRINT self.ACCEPTED_FACILITY_LEVEL = '1b' self._counter_of_failed_attempts_due_to_unavailable_medicines = 0 - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index 3c0bc2281c..834f962fce 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -2098,7 +2098,7 @@ def __init__( self.TREATMENT_ID = "Hiv_Test" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"VCTNegative": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """Do the testing and referring to other services""" @@ -2236,7 +2236,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MaleCirc": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.number_of_occurrences = 0 - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """ Do the circumcision for this man. If he is already circumcised, this is a follow-up appointment.""" @@ -2258,7 +2258,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "li_is_circ"] = True # Update equipment - self.EQUIPMENT.update({'Drip stand', 'Stool, adjustable height', 'Autoclave', + self.add_equipment({'Drip stand', 'Stool, adjustable height', 'Autoclave', 'Bipolar Diathermy Machine', 'Bed, adult', 'Trolley, patient'}) # Schedule follow-up appts @@ -2297,7 +2297,7 @@ def __init__(self, module, person_id, referred_from, repeat_visits): self.ACCEPTED_FACILITY_LEVEL = '1a' self.referred_from = referred_from self.repeat_visits = repeat_visits - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """ @@ -2366,7 +2366,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Hiv_Prevention_Prep" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"PharmDispensing": 1, "VCTNegative": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """Start PrEP for this person; or continue them on PrEP for 3 more months""" @@ -2428,7 +2428,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.counter_for_drugs_not_available = 0 self.counter_for_did_not_run = 0 - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """This is a Health System Interaction Event - start or continue HIV treatment for 6 more months""" diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index aadf9976aa..198b02f784 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1868,7 +1868,7 @@ def refer_for_cs(): if avail and sf_check: # Update equipment - hsi_event.EQUIPMENT.update({'Delivery Forceps', 'Vacuum extractor'}) + hsi_event.add_equipment({'Delivery Forceps', 'Vacuum extractor'}) pregnancy_helper_functions.log_met_need(self, f'avd_{indication}', hsi_event) @@ -2122,7 +2122,7 @@ def surgical_management_of_pph(self, hsi_event): # Update equipment # Todo: link to surgical equipment package when that exsists - hsi_event.EQUIPMENT.update( + hsi_event.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) # We apply a probability that surgical techniques will be effective @@ -2136,7 +2136,7 @@ def surgical_management_of_pph(self, hsi_event): # Update equipment # Todo: link to surgical equipment package when that exsists - hsi_event.EQUIPMENT.update( + hsi_event.add_equipment( {'Hysterectomy set'}) self.pph_treatment.set(person_id, 'hysterectomy') @@ -2150,7 +2150,7 @@ def surgical_management_of_pph(self, hsi_event): # Update equipment # Todo: link to surgical equipment package when that exsists - hsi_event.EQUIPMENT.update( + hsi_event.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) # log intervention delivery @@ -2179,7 +2179,7 @@ def blood_transfusion(self, hsi_event): hsi_event=hsi_event) if avail and sf_check: - hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) + 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) @@ -2205,7 +2205,7 @@ def assessment_and_treatment_of_anaemia(self, hsi_event): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info # Update equipment - hsi_event.EQUIPMENT.update({'Analyser, Haematology'}) + 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( @@ -2875,7 +2875,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'NormalDelivery': 1}) self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 2}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -2918,7 +2918,7 @@ def apply(self, person_id, squeeze_factor): optional='delivery_optional') # Log required equipment - self.EQUIPMENT.update({'Delivery set', 'Weighing scale', 'Stethoscope, foetal, monaural, Pinard, plastic', + self.add_equipment({'Delivery set', 'Weighing scale', 'Stethoscope, foetal, monaural, Pinard, plastic', 'Resuscitaire', 'Sphygmomanometer', 'Tray, emergency', 'Suction machine', 'Thermometer', 'Drip stand', 'Infusion pump'}) @@ -3056,7 +3056,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'PostnatalCare_Maternal' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = self._get_facility_level_for_pnc(person_id) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -3184,7 +3184,7 @@ def __init__(self, module, person_id, timing): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.timing = timing - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -3218,7 +3218,7 @@ def apply(self, person_id, squeeze_factor): # If intervention is delivered - update equipment # Todo: link to surgical equipment package when that exsists - self.EQUIPMENT.update( + self.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) person = df.loc[person_id] @@ -3254,7 +3254,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.EQUIPMENT.update( + self.add_equipment( {'Hysterectomy set'}) df.at[person_id, 'la_has_had_hysterectomy'] = True @@ -3323,7 +3323,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 5}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): logger.debug(key='message', data='HSI_Labour_PostnatalWardInpatientCare now running to capture ' diff --git a/src/tlo/methods/malaria.py b/src/tlo/methods/malaria.py index 44d30bb3ca..46a61d71b0 100644 --- a/src/tlo/methods/malaria.py +++ b/src/tlo/methods/malaria.py @@ -753,7 +753,7 @@ def __init__(self, module, person_id, facility_level='1a'): 'Under5OPD' if person_age_years < 5 else 'Over5OPD': 1} ) self.ACCEPTED_FACILITY_LEVEL = '1a' if (self.facility_level == '1a') else '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): @@ -851,7 +851,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Malaria_Test' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ConWithDCSA': 1}) self.ACCEPTED_FACILITY_LEVEL = '0' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): @@ -904,7 +904,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): @@ -997,7 +997,7 @@ def __init__(self, module, person_id): ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): @@ -1022,7 +1022,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ma_tx_counter'] += 1 # Update equipment - self.EQUIPMENT.update({'Drip stand', 'Haemoglobinometer', + self.add_equipment({'Drip stand', 'Haemoglobinometer', 'Analyser, Combined Chemistry and Electrolytes'}) # rdt is offered as part of the treatment package @@ -1055,7 +1055,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Malaria_Prevention_Iptp' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index 2ca79dea3a..0f4064da73 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -418,7 +418,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() # initialise empty set + self.set_equipment_essential_to_run_event({''}) # initialise empty set def apply(self, person_id, squeeze_factor): logger.debug(key="HSI_Measles_Treatment", @@ -445,7 +445,7 @@ def apply(self, person_id, squeeze_factor): if "respiratory_symptoms" in symptoms: # Update equipment - self.EQUIPMENT.update({'Oxygen concentrator', 'Oxygen cylinder, with regulator'}) + 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 0d5fced148..37d65020bb 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -1007,7 +1007,7 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): pregnancy_helper_functions.log_met_need(self, 'neo_sep_supportive_care', hsi_event) # Update equipment - hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump'}) + hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) # The same pattern is then followed for health centre care else: @@ -1020,7 +1020,7 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): pregnancy_helper_functions.log_met_need(self, 'neo_sep_abx', hsi_event) # Update equipment - hsi_event.EQUIPMENT.update({'Drip stand', 'Infusion pump', 'Oxygen cylinder, with regulator'}) + hsi_event.add_equipment({'Drip stand', 'Infusion pump', 'Oxygen cylinder, with regulator'}) def link_twins(self, child_one, child_two, mother_id): """ @@ -1386,7 +1386,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'PostnatalCare_Neonatal' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = self._get_facility_level_for_pnc(person_id) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): nci = self.module.newborn_care_info @@ -1478,7 +1478,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index 35ce1bb34b..03a84baa12 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -647,7 +647,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OesophagealCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # I think this will need endoscope and biopsy needle. Also lab equipment needed to perform histology. # I can't see endoscope in equipment list but it may be given a slightly different name @@ -676,7 +676,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available update equipment and run the dx_test representing the biopsy # n.b. endoscope not in equipment list - self.EQUIPMENT.update({'Endoscope', 'Ordinary Microscope'}) + 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( @@ -731,7 +731,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # equipment need here will be surgery @@ -770,7 +770,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available and the treatment will go ahead - update the equipment # TODO: link to surgical equipment package when that exists - self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', + self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) # Log chemotherapy consumables @@ -808,7 +808,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OesophagealCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # equipment: I assume endoscope needed for this @@ -867,7 +867,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # when radiology available then palliative radiology may be performed but suggest we don't need to include yet # not sure what equipment needed for Endoscopic stent placement or Feeding tube which are done as palliative @@ -889,7 +889,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment - self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + 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 aadf289388..e6f8ea6a9d 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -638,7 +638,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OtherAdultCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # equipment: investigations will differ by presenting symptom, but suggest we have biopsy and histology # and ultrasound @@ -665,7 +665,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available update equipment and run the dx_test representing the biopsy - self.EQUIPMENT.update({'Ultrasound scanning machine', 'Ordinary Microscope'}) + 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( @@ -722,7 +722,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available @@ -760,7 +760,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment # TODO: link to surgical equipment package when that exists - self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', + self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) # Record date and stage of starting treatment @@ -796,7 +796,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OtherAdultCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # equipment: some checks will involve further biopsy, ultrasound, histology @@ -859,7 +859,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # equipment: in general not required I don't think def apply(self, person_id, squeeze_factor): @@ -878,7 +878,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment - self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + 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 406a0208ed..59e9fdf966 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -1282,7 +1282,7 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self.ALERT_OTHER_DISEASES = [] self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 2cf277ec5a..f98003751b 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -674,7 +674,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. @@ -726,7 +726,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. @@ -777,7 +777,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. @@ -803,7 +803,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available update the use of equipment and run the dx_test representing the biopsy - self.EQUIPMENT.update({'Ultrasound scanning machine', 'Ordinary Microscope'}) + 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( @@ -858,7 +858,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # equipment as required for surgery @@ -898,7 +898,7 @@ def apply(self, person_id, squeeze_factor): # If consumables are available and the treatment will go ahead - update the equipment # TODO: link to surgical equipment package when that exists - self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Laparotomy Set', + self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) # Record date and stage of starting treatment @@ -931,7 +931,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # possibly biopsy and histology @@ -990,7 +990,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) # generally not sure equipment is required as therapy is with drug, but can require castration surgery @@ -1010,7 +1010,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment - self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + 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 034f1cfebf..2ac289238a 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3121,7 +3121,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_Imaging' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'DiagRadio': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): self.sim.population.props.at[person_id, 'rt_diagnosed'] = True @@ -3131,11 +3131,11 @@ def apply(self, person_id, squeeze_factor): if 'DiagRadio'in list(self.EXPECTED_APPT_FOOTPRINT.keys()): # TODO: use xray package when available # TODO: robbie did not log the 'xray consumable' here as done in other modules (Tb) - self.EQUIPMENT.update({'X-ray machine', 'X-ray viewer'}) + self.add_equipment({'X-ray machine', 'X-ray viewer'}) elif 'Tomography' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): self.ACCEPTED_FACILITY_LEVEL = '3' - self.EQUIPMENT.update({'Computed Tomography (CT machine)', 'CT scanner accessories'}) + self.add_equipment({'Computed Tomography (CT machine)', 'CT scanner accessories'}) def did_not_run(self, *args, **kwargs): pass @@ -3195,7 +3195,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 8}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) p = module.parameters # Load the parameters used in this event @@ -3441,7 +3441,7 @@ def apply(self, person_id, squeeze_factor): # TODO: some general ICU equipment listed below? additional would need to be added dependent on severity # of illness - self.EQUIPMENT.update({ + self.add_equipment({ 'Patient monitor', 'Blood pressure machine', 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope'}) @@ -3746,7 +3746,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_ShockTreatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -3787,7 +3787,7 @@ def apply(self, person_id, squeeze_factor): data=f"Hypovolemic shock treatment available for person {person_id}") df.at[person_id, 'rt_in_shock'] = False - self.EQUIPMENT.update({'Infusion pump', 'Drip stand', 'Oxygen cylinder, with regulator', 'Nasal Prongs'}) + self.add_equipment({'Infusion pump', 'Drip stand', 'Oxygen cylinder, with regulator', 'Nasal Prongs'}) else: self.sim.modules['RTI'].schedule_hsi_event_for_tomorrow(self) @@ -3843,7 +3843,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_FractureCast' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): # Get the population and health system @@ -3890,7 +3890,7 @@ def apply(self, person_id, squeeze_factor): f"{person_id}" ) - self.EQUIPMENT.update({'Casting platform', 'Casting chairs', 'Bucket, 10L'}) + 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 @@ -3983,7 +3983,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_OpenFractureTreatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MinorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -4031,7 +4031,7 @@ def apply(self, person_id, squeeze_factor): # TODO: open fractures are usually treated surgically so more equipment is likely required. # Not immediately clear if these individuals are also scheduled to the surgical HSIs. will leave for now - self.EQUIPMENT.update({'Infusion pump', 'Drip stand'}) + self.add_equipment({'Infusion pump', 'Drip stand'}) person = df.loc[person_id] # update the dataframe to show this person is recieving treatment @@ -4102,7 +4102,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name @@ -4198,7 +4198,7 @@ def __init__(self, module, person_id): p = self.module.parameters self.prob_mild_burns = p['prob_mild_burns'] - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name @@ -4302,7 +4302,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_TetanusVaccine' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'EPI': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -4364,7 +4364,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -4667,7 +4667,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({}) - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) p = self.module.parameters self.prob_perm_disability_with_treatment_severe_TBI = p['prob_perm_disability_with_treatment_severe_TBI'] @@ -4759,7 +4759,7 @@ def apply(self, person_id, squeeze_factor): # TODO: not all surgeries conducted here are laparotomies however likely to have most appropriate equipment # so should be fine for now - self.EQUIPMENT.update({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) + self.add_equipment({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) # ------------------------ Track permanent disabilities with treatment ------------------------------------- # --------------------------------- Perm disability from TBI ----------------------------------------------- @@ -5014,7 +5014,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_MinorSurgeries' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MinorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -5078,7 +5078,7 @@ def apply(self, person_id, squeeze_factor): # outcomes if request_outcome: # TODO: link to surgical equipment package when that exists - self.EQUIPMENT.update({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) + self.add_equipment({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) # create a dictionary to store the recovery times for each injury in days minor_surg_recov_time_days = { diff --git a/src/tlo/methods/schisto.py b/src/tlo/methods/schisto.py index 081f1be54a..9d69c9cce9 100644 --- a/src/tlo/methods/schisto.py +++ b/src/tlo/methods/schisto.py @@ -899,7 +899,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Schisto_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD' if under_5 else 'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() # initialise empty set + self.set_equipment_essential_to_run_event({''}) # initialise empty set self._num_occurrences = 0 def apply(self, person_id, squeeze_factor): @@ -917,7 +917,7 @@ def apply(self, person_id, squeeze_factor): ) if will_test: - self.EQUIPMENT.update({'Ordinary Microscope'}) + 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() @@ -960,7 +960,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Schisto_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD' if under_5 else 'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() # initialise empty set + self.set_equipment_essential_to_run_event({''}) # initialise empty set def apply(self, person_id, squeeze_factor): """Do the treatment for this person.""" @@ -991,7 +991,7 @@ def __init__(self, module, person_id, beneficiaries_ids: Optional[Sequence] = No # `self.EXPECTED_APPT_FOOTPRINT` show that this requires 1 * that appointment type. self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() # initialise empty set + self.set_equipment_essential_to_run_event({''}) # initialise empty set def apply(self, person_id, squeeze_factor): """Provide the treatment to the beneficiaries of this HSI.""" diff --git a/src/tlo/methods/stunting.py b/src/tlo/methods/stunting.py index 004deb0d11..fd77f3e954 100644 --- a/src/tlo/methods/stunting.py +++ b/src/tlo/methods/stunting.py @@ -533,7 +533,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Undernutrition_Feeding' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'U5Malnutr': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 691412dc96..4a3e7e83a6 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -1721,7 +1721,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Screening" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """Do the screening and referring to next tests""" @@ -1817,7 +1817,7 @@ def apply(self, person_id, squeeze_factor): ) if test_result is not None: # Update equipment - self.EQUIPMENT.update({'Sputum Collection box', 'Ordinary Microscope'}) + self.add_equipment({'Sputum Collection box', 'Ordinary Microscope'}) elif test == "xpert": ACTUAL_APPT_FOOTPRINT = self.make_appt_footprint( @@ -1835,7 +1835,7 @@ def apply(self, person_id, squeeze_factor): ) if test_result is not None: # Update equipment - self.EQUIPMENT.update({'Sputum Collection box', 'Gene Expert (16 Module)'}) + self.add_equipment({'Sputum Collection box', 'Gene Expert (16 Module)'}) # ------------------------- testing referrals ------------------------- # @@ -1856,7 +1856,7 @@ def apply(self, person_id, squeeze_factor): ) if test_result is not None: # Update equipment - self.EQUIPMENT.update({'Sputum Collection box', 'Ordinary Microscope'}) + self.add_equipment({'Sputum Collection box', 'Ordinary Microscope'}) # if still no result available, rely on clinical diagnosis if test_result is None: @@ -1966,7 +1966,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Clinical" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Under5OPD": 0.5}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """ Do the screening and referring process """ @@ -2034,7 +2034,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Xray" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"DiagRadio": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): @@ -2058,7 +2058,7 @@ def apply(self, person_id, squeeze_factor): ) if test_result is not None: # Update equipment - self.EQUIPMENT.update({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items + self.add_equipment({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items # if consumables not available, refer to level 2 # return blank footprint as xray did not occur @@ -2109,7 +2109,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Xray" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"DiagRadio": 1}) self.ACCEPTED_FACILITY_LEVEL = '2' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): @@ -2133,7 +2133,7 @@ def apply(self, person_id, squeeze_factor): ) if test_result is not None: # Update equipment - self.EQUIPMENT.update({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items + self.add_equipment({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items # if consumables not available, rely on clinical diagnosis # return blank footprint as xray was not available @@ -2181,7 +2181,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Treatment" self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) self.number_of_occurrences = 0 @property @@ -2330,7 +2330,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Test_FollowUp" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"TBFollowUp": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): p = self.module.parameters @@ -2396,7 +2396,7 @@ def apply(self, person_id, squeeze_factor): ) if test_result is not None: # Update equipment - self.EQUIPMENT.update({'Sputum Collection box', 'Ordinary Microscope'}) + 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"]: @@ -2413,7 +2413,7 @@ def apply(self, person_id, squeeze_factor): ) if xperttest_result is not None: # Update equipment - self.EQUIPMENT.update({'Sputum Collection box', 'Gene Expert (16 Module)'}) + 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"): @@ -2468,7 +2468,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Prevention_Ipt" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.EQUIPMENT = set() + self.set_equipment_essential_to_run_event({''}) self.number_of_occurrences = 0 def apply(self, person_id, squeeze_factor): From 30930829f48dbd9be18e45b6d9f78d82a162cf87 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 5 Feb 2024 21:25:22 +0000 Subject: [PATCH 323/443] [no ci] modules: comments updated --- src/tlo/methods/alri.py | 4 ++-- src/tlo/methods/breast_cancer.py | 8 ++++---- src/tlo/methods/care_of_women_during_pregnancy.py | 6 +++--- src/tlo/methods/contraception.py | 2 +- src/tlo/methods/hiv.py | 2 +- src/tlo/methods/labour.py | 12 ++++++------ src/tlo/methods/malaria.py | 2 +- src/tlo/methods/measles.py | 2 +- src/tlo/methods/newborn_outcomes.py | 4 ++-- src/tlo/methods/oesophagealcancer.py | 2 +- src/tlo/methods/other_adult_cancers.py | 2 +- src/tlo/methods/tb.py | 14 +++++++------- 12 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index 7c80223763..1d50ed7908 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2654,11 +2654,11 @@ 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 requires oxygen, update equipment + # If individual requires oxygen, add used equipment if oxygen_provided: self.add_equipment({'Oxygen cylinder, with regulator', 'Nasal Prongs'}) - # If individual requires intravenous antibiotics, update equipment + # If individual requires 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'}) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index e534441cc8..d3e9a2170d 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -687,7 +687,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # Use a biopsy to diagnose whether the person has breast Cancer - # If consumables are available update the use of equipment and 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( @@ -790,7 +790,7 @@ def apply(self, person_id, squeeze_factor): ) if cons_available: - # If consumables are available and the treatment will go ahead - update the equipment + # If consumables are available and the treatment will go ahead - add the used equipment # TODO: link to surgical equipment package when that exists self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -805,7 +805,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] # TODO: Eva's dummy equipment example - it needs to be replaced by real items from the RF - # Update equipment + # Add used equipment # self.add_equipment({'Anything used for mastectomy'}) # Schedule a post-treatment check for 12 months: @@ -914,7 +914,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: - # If consumables are available and the treatment will go ahead - update the equipment + # 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 diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 86168748ad..37e93cb640 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1462,7 +1462,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - # Update equipment used during first ANC visit not directly related to interventions + # Add equipment used during first ANC visit not directly related to interventions self.add_equipment( {'Weighing scale', 'Height Pole (Stadiometer)', 'MUAC tape', 'Measuring tapes', 'Ultrasound, combined 2/4 pole interferential with vacuum and dual frequency 1-3MHZ'}) @@ -1556,7 +1556,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - # Update equipment used during ANC visit not directly related to interventions + # Add equipment used during ANC visit not directly related to interventions self.add_equipment( {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' 'Ultrasound, combined 2/4 pole interferential with vacuum and dual frequency 1-3MHZ'}) @@ -2628,7 +2628,7 @@ def apply(self, person_id, squeeze_factor): sf='retained_prod', hsi_event=self) - # Update equipment if intervention can happen + # 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'}) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 1ed54e72ba..a34a2370a8 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1285,7 +1285,7 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Update equipment + # Add used equipment if _new_contraceptive == 'female_sterilization': self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index 834f962fce..c0de74ab08 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -2257,7 +2257,7 @@ def apply(self, person_id, squeeze_factor): # Update circumcision state df.at[person_id, "li_is_circ"] = True - # Update equipment + # Add used equipment self.add_equipment({'Drip stand', 'Stool, adjustable height', 'Autoclave', 'Bipolar Diathermy Machine', 'Bed, adult', 'Trolley, patient'}) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 198b02f784..496225b07b 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -1867,7 +1867,7 @@ def refer_for_cs(): if avail and sf_check: - # Update equipment + # Add used equipment hsi_event.add_equipment({'Delivery Forceps', 'Vacuum extractor'}) pregnancy_helper_functions.log_met_need(self, f'avd_{indication}', hsi_event) @@ -2120,7 +2120,7 @@ def surgical_management_of_pph(self, hsi_event): if avail and sf_check: - # Update equipment + # Add used equipment # Todo: link to surgical equipment package when that exsists hsi_event.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -2134,7 +2134,7 @@ def surgical_management_of_pph(self, hsi_event): else: # If the treatment is unsuccessful then women will require a hysterectomy to stop the bleeding - # Update equipment + # Add used equipment # Todo: link to surgical equipment package when that exsists hsi_event.add_equipment( {'Hysterectomy set'}) @@ -2148,7 +2148,7 @@ def surgical_management_of_pph(self, hsi_event): and sf_check and avail): self.pph_treatment.set(person_id, 'surgery') - # Update equipment + # Add used equipment # Todo: link to surgical equipment package when that exsists hsi_event.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -2204,7 +2204,7 @@ def assessment_and_treatment_of_anaemia(self, hsi_event): mother = df.loc[person_id] mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info - # Update equipment + # Add used equipment hsi_event.add_equipment({'Analyser, Haematology'}) # Use dx_test function to assess anaemia status @@ -3216,7 +3216,7 @@ def apply(self, person_id, squeeze_factor): if avail and sf_check or (mni[person_id]['cs_indication'] == 'other'): - # If intervention is delivered - update equipment + # If intervention is delivered - add used equipment # Todo: link to surgical equipment package when that exsists self.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) diff --git a/src/tlo/methods/malaria.py b/src/tlo/methods/malaria.py index 46a61d71b0..545108f435 100644 --- a/src/tlo/methods/malaria.py +++ b/src/tlo/methods/malaria.py @@ -1021,7 +1021,7 @@ 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 - # Update equipment + # Add used equipment self.add_equipment({'Drip stand', 'Haemoglobinometer', 'Analyser, Combined Chemistry and Electrolytes'}) diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index 0f4064da73..34f4735ef6 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -444,7 +444,7 @@ def apply(self, person_id, squeeze_factor): data=f"HSI_Measles_Treatment: giving required measles treatment to person {person_id}") if "respiratory_symptoms" in symptoms: - # Update equipment + # Add used equipment self.add_equipment({'Oxygen concentrator', 'Oxygen cylinder, with regulator'}) # modify person property which is checked when scheduled death occurs (or shouldn't occur) diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 37d65020bb..5e548e5284 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -1006,7 +1006,7 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): df.at[person_id, 'nb_supp_care_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_supportive_care', hsi_event) - # Update equipment + # Add used equipment hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) # The same pattern is then followed for health centre care @@ -1019,7 +1019,7 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): df.at[person_id, 'nb_inj_abx_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_abx', hsi_event) - # Update equipment + # Add used equipment 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 03a84baa12..b41a6a9a48 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -674,7 +674,7 @@ def apply(self, person_id, squeeze_factor): self.module.item_codes_oesophageal_can['screening_biopsy_optional']) if cons_avail: - # If consumables are available update equipment and 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'}) diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index e6f8ea6a9d..f564b5ada0 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -664,7 +664,7 @@ def apply(self, person_id, squeeze_factor): self.module.item_codes_other_can['screening_biopsy_optional']) if cons_avail: - # If consumables are available update equipment and 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: diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 4a3e7e83a6..9481292618 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -1816,7 +1816,7 @@ def apply(self, person_id, squeeze_factor): dx_tests_to_run="tb_clinical", hsi_event=self ) if test_result is not None: - # Update equipment + # Add used equipment self.add_equipment({'Sputum Collection box', 'Ordinary Microscope'}) elif test == "xpert": @@ -1834,7 +1834,7 @@ 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: - # Update equipment + # Add used equipment self.add_equipment({'Sputum Collection box', 'Gene Expert (16 Module)'}) # ------------------------- testing referrals ------------------------- # @@ -1855,7 +1855,7 @@ def apply(self, person_id, squeeze_factor): {"Over5OPD": 2, "LabTBMicro": 1} ) if test_result is not None: - # Update equipment + # Add used equipment self.add_equipment({'Sputum Collection box', 'Ordinary Microscope'}) # if still no result available, rely on clinical diagnosis @@ -2057,7 +2057,7 @@ def apply(self, person_id, squeeze_factor): dx_tests_to_run="tb_xray_smear_negative", hsi_event=self ) if test_result is not None: - # Update equipment + # Add used equipment self.add_equipment({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items # if consumables not available, refer to level 2 @@ -2132,7 +2132,7 @@ def apply(self, person_id, squeeze_factor): dx_tests_to_run="tb_xray_smear_negative", hsi_event=self ) if test_result is not None: - # Update equipment + # Add used equipment self.add_equipment({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items # if consumables not available, rely on clinical diagnosis @@ -2395,7 +2395,7 @@ def apply(self, person_id, squeeze_factor): dx_tests_to_run="tb_sputum_test_smear_negative", hsi_event=self ) if test_result is not None: - # Update equipment + # 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 @@ -2412,7 +2412,7 @@ def apply(self, person_id, squeeze_factor): dx_tests_to_run="tb_xpert_test_smear_negative", hsi_event=self ) if xperttest_result is not None: - # Update equipment + # Add used equipment self.add_equipment({'Sputum Collection box', 'Gene Expert (16 Module)'}) # if xpert test returns new mdr-tb diagnosis From caa35453ccb966d2bb82711e8226deae561719e0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 14 Feb 2024 23:44:03 +0000 Subject: [PATCH 324/443] la & ri: comments updated --- src/tlo/methods/alri.py | 4 ++-- src/tlo/methods/labour.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index 1d50ed7908..04589b7013 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2654,11 +2654,11 @@ 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 requires oxygen, add used equipment + # If individual is provided with oxygen, add used equipment if oxygen_provided: self.add_equipment({'Oxygen cylinder, with regulator', 'Nasal Prongs'}) - # If individual requires intravenous antibiotics, add used equipment + # 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'}) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 496225b07b..ca931702a7 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -2917,7 +2917,7 @@ def apply(self, person_id, squeeze_factor): self.module, self, self.module.item_codes_lab_consumables, core='delivery_core', optional='delivery_optional') - # Log required equipment + # 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'}) From 09b97cf3609912d98bc649f88c7c6383199f954d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 15 Feb 2024 00:29:10 +0000 Subject: [PATCH 325/443] cmd: TODO surgical pkg --- src/tlo/methods/cardio_metabolic_disorders.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index 1afb15dd21..7ff996fac2 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1769,6 +1769,7 @@ def apply(self, person_id, squeeze_factor): data=('This is HSI_CardioMetabolicDisorders_SeeksEmergencyCareAndGetsTreatment: ' f'The squeeze-factor is {squeeze_factor}.'), ) + # TODO: link to surgical equipment package when that exists self.add_equipment({'Analyser, Combined Chemistry and Electrolytes', 'Analyser, Haematology', 'Patient monitor', 'Drip stand', From 8993a5aaeb439870b70725dfb08d813a983d1724 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 15 Feb 2024 00:33:05 +0000 Subject: [PATCH 326/443] hsi_gen_1st_appts: empty equip declarations added --- src/tlo/methods/hsi_generic_first_appts.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tlo/methods/hsi_generic_first_appts.py b/src/tlo/methods/hsi_generic_first_appts.py index f84518b4a3..6e589ee9df 100644 --- a/src/tlo/methods/hsi_generic_first_appts.py +++ b/src/tlo/methods/hsi_generic_first_appts.py @@ -59,6 +59,7 @@ def __init__(self, module, person_id, facility_level='0'): # for this person. In some cases, small bits # of care are provided (e.g. a diagnosis, or # the provision of inhaler.). + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """Run the actions required during the HSI.""" @@ -86,6 +87,7 @@ def __init__(self, module, person_id): # for this person. In some cases, small bits # of care are provided (e.g. a diagnosis, or # the provision of inhaler.). + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): @@ -107,6 +109,7 @@ def __init__(self, module, person_id, accepted_facility_level='1a'): self.TREATMENT_ID = "FirstAttendance_SpuriousEmergencyCare" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = accepted_facility_level # '1a' in default or '1b' as an alternative + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 0b3268540941c01a8b2771e3a38e2a3f83446a54 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 15 Feb 2024 00:40:22 +0000 Subject: [PATCH 327/443] RF_Equip: added Source_Equip_Item for Endoscope --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 66cfcb8ad7..be89a2177f 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:103d6c8c46eaf336672bba5d43a959f6056d806cd7fa66d74c8bd25672bbdd07 -size 33213 +oid sha256:3489419094ed7a37987358fba7734b45410cc77750b3e560708739bef2536ba5 +size 33268 From 3b1f8e5bd44d1e191d0720bf4673a74923201af5 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 22 Feb 2024 13:51:26 +0000 Subject: [PATCH 328/443] rti fixes --- src/tlo/methods/rti.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 2ac289238a..5dbae60ffb 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3128,7 +3128,7 @@ def apply(self, person_id, squeeze_factor): road_traffic_injuries = self.sim.modules['RTI'] road_traffic_injuries.rti_injury_diagnosis(person_id, self.EXPECTED_APPT_FOOTPRINT) - if 'DiagRadio'in list(self.EXPECTED_APPT_FOOTPRINT.keys()): + if 'DiagRadio' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): # TODO: use xray package when available # TODO: robbie did not log the 'xray consumable' here as done in other modules (Tb) self.add_equipment({'X-ray machine', 'X-ray viewer'}) @@ -4029,9 +4029,9 @@ def apply(self, person_id, squeeze_factor): data=f"Fracture casts available for person {person_id} {open_fracture_counts} open fractures" ) - # TODO: open fractures are usually treated surgically so more equipment is likely required. - # Not immediately clear if these individuals are also scheduled to the surgical HSIs. will leave for now - self.add_equipment({'Infusion pump', 'Drip stand'}) + # Todo: link to surgical equipment package when that exsists + self.add_equipment( + {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) person = df.loc[person_id] # update the dataframe to show this person is recieving treatment From ff4d0729c2b47c7616a457ff88ec12a8db5ac127 Mon Sep 17 00:00:00 2001 From: sm2511 Date: Tue, 5 Mar 2024 13:08:45 +0000 Subject: [PATCH 329/443] Add cystoscope, endoscope and prostate specific antigen test to consumable list - add three consumables to `ResourceFile_Consumables_Items_and_Packages.csv` using the `generate_consumables_item_codes_and_packages.py` script - correct the spelling of cystoscope (from cytoscope) --- ...rceFile_Consumables_Items_and_Packages.csv | 4 +- .../ResourceFile_consumables_matched.csv | 2 +- ...ate_consumables_item_codes_and_packages.py | 37 ++++++++++++++++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/resources/healthsystem/consumables/ResourceFile_Consumables_Items_and_Packages.csv b/resources/healthsystem/consumables/ResourceFile_Consumables_Items_and_Packages.csv index 8af8f070b2..ef6bcbda8e 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:85e2c3ba8037e74490751fbb8384709dff1907c785c856f0394f40b4fc024da3 -size 253400 +oid sha256:144511462756e1db1eeab2745d04ccca9a89c93499b5fb3d493d1dba1732f22b +size 249275 diff --git a/resources/healthsystem/consumables/ResourceFile_consumables_matched.csv b/resources/healthsystem/consumables/ResourceFile_consumables_matched.csv index 7754d65118..f0da7695b5 100644 --- a/resources/healthsystem/consumables/ResourceFile_consumables_matched.csv +++ b/resources/healthsystem/consumables/ResourceFile_consumables_matched.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fbfe91222d3a2a32ed44a4be711b30c5323276a71df802f6c9249eb4c21f8d43 +oid sha256:cdc77f0229a2bf37e9629edd54cb5df80aa00b5bde7b94c74adfecc4f836e3bd size 90158 diff --git a/src/scripts/data_file_processing/healthsystem/consumables/processing_data_from_one_health/generate_consumables_item_codes_and_packages.py b/src/scripts/data_file_processing/healthsystem/consumables/processing_data_from_one_health/generate_consumables_item_codes_and_packages.py index 7681bcd35d..0b356e7cd0 100644 --- a/src/scripts/data_file_processing/healthsystem/consumables/processing_data_from_one_health/generate_consumables_item_codes_and_packages.py +++ b/src/scripts/data_file_processing/healthsystem/consumables/processing_data_from_one_health/generate_consumables_item_codes_and_packages.py @@ -20,7 +20,8 @@ # Set local Dropbox source path_to_dropbox = Path( # <-- point to the TLO dropbox locally - '/Users/tbh03/Dropbox (SPH Imperial College)/Thanzi la Onse Theme 1 SHARE') + #'/Users/tbh03/Dropbox (SPH Imperial College)/Thanzi la Onse Theme 1 SHARE' + '/Users/sm2511/Dropbox/Thanzi la Onse') resourcefilepath = Path("./resources") path_for_new_resourcefiles = resourcefilepath / "healthsystem/consumables" @@ -308,6 +309,40 @@ ignore_index=True ) + +cons = cons.append({ + 'Intervention_Cat': "Added by SM (Recommended by EJ)", + 'Intervention_Pkg': "Misc", + 'Intervention_Pkg_Code': -99, + 'Items': "Cystoscope", + 'Item_Code': 285, + 'Expected_Units_Per_Case': 1.0, + 'Unit_Cost': np.nan}, + ignore_index=True +) + +cons = cons.append({ + 'Intervention_Cat': "Added by SM (Recommended by EJ)", + 'Intervention_Pkg': "Misc", + 'Intervention_Pkg_Code': -99, + 'Items': "Endoscope", + 'Item_Code': 280, + 'Expected_Units_Per_Case': 1.0, + 'Unit_Cost': np.nan}, + ignore_index=True +) + +cons = cons.append({ + 'Intervention_Cat': "Added by SM (Recommended by EJ)", + 'Intervention_Pkg': "Misc", + 'Intervention_Pkg_Code': -99, + 'Items': "Prostate specific antigen test", + 'Item_Code': 281, + 'Expected_Units_Per_Case': 1.0, + 'Unit_Cost': np.nan}, + ignore_index=True +) + # -------------- # -------------- # -------------- From 4d8712f403e86fcbedd90f057f105bc109b6eefd Mon Sep 17 00:00:00 2001 From: sm2511 Date: Tue, 5 Mar 2024 14:38:36 +0000 Subject: [PATCH 330/443] Update the availability of Prostate specific antigen test to be the same as that of prostate biopsy. This is based on an assumption agreed with Andrew. All assumptions on availability of consumables not found in the OpenLMIS or HHFA datasets are in the `ResourceFile_hhfa_consumables.xlsx` file in Dropbox - `availability_assumptions` tab. This is all described in the `Description of derivation of stockout rates from OpenLMIS data_Final.docx` file in Dropbox. --- .../ResourceFile_Consumables_availability_small.csv | 4 ++-- .../consumables_availability_estimation.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/resources/healthsystem/consumables/ResourceFile_Consumables_availability_small.csv b/resources/healthsystem/consumables/ResourceFile_Consumables_availability_small.csv index 533c9a0821..191521c27a 100644 --- a/resources/healthsystem/consumables/ResourceFile_Consumables_availability_small.csv +++ b/resources/healthsystem/consumables/ResourceFile_Consumables_availability_small.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0b53caa5f750cf9810c901aeeb993470eceea8ecc4119f5183f996607c8e252e -size 6389877 +oid sha256:f846a71ce134793bacbdaffbbdca8914617d3a70dd1b736b2302afd9e06fbca5 +size 6087716 diff --git a/src/scripts/data_file_processing/healthsystem/consumables/consumable_resource_analyses_with_lmis/consumables_availability_estimation.py b/src/scripts/data_file_processing/healthsystem/consumables/consumable_resource_analyses_with_lmis/consumables_availability_estimation.py index 5e7e9d22f1..18b355b2f4 100644 --- a/src/scripts/data_file_processing/healthsystem/consumables/consumable_resource_analyses_with_lmis/consumables_availability_estimation.py +++ b/src/scripts/data_file_processing/healthsystem/consumables/consumable_resource_analyses_with_lmis/consumables_availability_estimation.py @@ -32,7 +32,7 @@ # Set local Dropbox source path_to_dropbox = Path( # <-- point to the TLO dropbox locally - 'C:/Users/sm2511/Dropbox/Thanzi la Onse' + '/Users/sm2511/Dropbox/Thanzi la Onse' # '/Users/sejjj49/Dropbox/Thanzi la Onse' # 'C:/Users/tmangal/Dropbox/Thanzi la Onse' ) @@ -207,7 +207,7 @@ def change_colnames(df, NameChangeList): # Change column names 'Unigold HIV test kits, Kit of 20 Tests': '''Unigold HIV Test Kits''', 'Determine HIV test Kits, Kit of 100 Tests': '''Determine HIV Test Kits''', 'Abacavir/Lamivudine (ABC/3TC), 60+30mg': '''Abacavir (ABC) + Lamivudine(3TC), 60mg+30mg, 60''S (9P)''', - 'Atazanavir /Ritonavir (ATV/r), 300+100mg': 'Atazanavir + Ritonavir, 300mg + 100mg, 30''S (7A)', + 'Atazanavir /Ritonavir (ATV/r), 300+100mg': '''Atazanavir + Ritonavir, 300mg + 100mg, 30''S (7A)''', 'SD Bioline, Syphilis test kits, Kit of 30 Tests': 'Determine Syphillis Test Kits', 'Isoniazid tablets, 100mg': '''Isoniazid 100mg''', 'Isoniazid tablets, 300mg': '''Isoniazid 300mg''', From 5209b331f2a8e3646eb6f974edc8200577f8947f Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Sep 2023 12:11:31 +0100 Subject: [PATCH 331/443] TODO: some modules use equipment when talking about consumables --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 ++ src/tlo/methods/labour.py | 2 ++ src/tlo/methods/newborn_outcomes.py | 2 ++ src/tlo/methods/rti.py | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 42e026478c..9fe9eb4215 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -205,6 +205,8 @@ def get_and_store_pregnancy_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_preg_consumables['blood_test_equipment'] = \ get_list_of_items(self, ['Blood collecting tube, 5 ml', 'Cannula iv (winged with injection pot) 18_each_CMST', diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 0f6d7d134e..3fa400f2f0 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -686,6 +686,8 @@ def get_and_store_labour_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_lab_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 513b644746..7c240f8bc7 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -380,6 +380,8 @@ def get_and_store_newborn_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing self.item_codes_nb_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index aac0129cf6..0d788aaf6c 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4665,6 +4665,8 @@ def apply(self, person_id, squeeze_factor): # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, # equipment used by surgeon, gloves and facemask + # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is + # confusing get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From 5158aa22a6c3cb848f352c05b7e52fdbc1530574 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:11:12 +0100 Subject: [PATCH 332/443] breast_cancer: dummy used_equipment added where Andrew requested diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 1ce9ad2bf..56c935fba 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -666,6 +666,8 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -759,6 +761,9 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Record used equipment + self.used_equipment = 'Anything used for mastectomy as I guess this is about' + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( --- src/tlo/methods/breast_cancer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1ce9ad2bf6..56c935fba2 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -666,6 +666,8 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this + self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', + 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -759,6 +761,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # Record used equipment + self.used_equipment = 'Anything used for mastectomy as I guess this is about' + # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( From e554dd74bb40e466e8a93bb33039951cbe4db8e0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:15:40 +0100 Subject: [PATCH 333/443] co: dummy used_equipment added for methods where Emi listed some --- src/tlo/methods/contraception.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 9ddbc6aac9..4def5a649d 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1170,7 +1170,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 record 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: @@ -1194,6 +1195,13 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive + + # Record used equipment when needed + if _new_contraceptive == 'female_sterilization': + self.used_equipment = {'Smt used to sterilize a woman'} + elif _new_contraceptive == 'IUD': + self.used_equipment = {'Equipment used when performing IUD'} + else: _new_contraceptive = "not_using" From e4935e36ec29225a71d7ebe2deca931ed31fb561 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 00:24:02 +0100 Subject: [PATCH 334/443] healthsystem: annual equipment summary log by fac. level --- src/tlo/methods/healthsystem.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 6838f56705..4fd39fcb25 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,6 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + self.used_equipment = set() @property def bed_days_allocated_to_this_event(self): @@ -1810,6 +1811,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, + equipment=hsi_event.used_equipment, ) def write_to_hsi_log( @@ -1820,6 +1822,7 @@ def write_to_hsi_log( squeeze_factor: float, did_run: bool, priority: int, + equipment: set, ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1849,6 +1852,7 @@ def write_to_hsi_log( squeeze_factor=squeeze_factor, appt_footprint=event_details.appt_footprint, level=event_details.facility_level, + equipment=equipment, ) def call_and_record_never_ran_hsi_event(self, hsi_event, priority=None): @@ -2671,6 +2675,7 @@ def apply(self, population): squeeze_factor=0.0, priority=-1, did_run=True, + equipment=set() # TODO: explore more, should it be non-emtpy in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2725,6 +2730,7 @@ def _reset_internal_stores(self) -> None: self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} # <--Same as `self._appts` but also split by facility_level + self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran @@ -2741,7 +2747,8 @@ def record_hsi_event(self, hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str + level: str, + equipment: set ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2758,6 +2765,9 @@ def record_hsi_event(self, self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number + # Update used equipment by level + self._equip_by_level[level].update(equipment) + def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2820,6 +2830,14 @@ def write_to_log_and_reset_counters(self): }, ) + logger_summary.info( + key="Equipment", + description="Sets of used equipment for each facility level in this calendar year.", + data={ + "Equipment_By_Level": self._equip_by_level, + }, + ) + self._reset_internal_stores() From 8b455bd6726d6b59e196aae3773a566cb0b00381 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Sep 2023 16:15:18 +0100 Subject: [PATCH 335/443] breast_cancer: mastectomy dummy equipment fixed --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 56c935fba2..5d8fabfcb2 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -762,7 +762,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] # Record used equipment - self.used_equipment = 'Anything used for mastectomy as I guess this is about' + self.used_equipment = {'Anything used for mastectomy as I guess this is about'} # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( From 9f44e8560c8a151953f1f3cdb02ace3203bf2f06 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Sep 2023 21:01:35 +0100 Subject: [PATCH 336/443] equipment_catalogue & utils: new script + a change in utils.py - to create equip. catalogue --- .../equipment/equipment_catalogue.py | 101 ++++++++++++++++++ src/tlo/analysis/utils.py | 8 +- 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 src/scripts/healthsystem/equipment/equipment_catalogue.py diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py new file mode 100644 index 0000000000..c88a469c2f --- /dev/null +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -0,0 +1,101 @@ +import argparse +import pandas as pd +from pathlib import Path +from tlo.analysis.utils import extract_results +from functools import reduce + + +def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: + """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated + year.""" + + def get_equipment_declaration_by_levels(_df): + """Get the equipment declaration by facility levels for the year.""" + + def unpack_dict_in_series(_raw: pd.Series): + # Create an empty DataFrame to store the data + df = pd.DataFrame() + + # Iterate through the dictionary items + for col_name, mydict in _raw.items(): + for date, inner_dict in mydict.items(): + # Convert the inner_dict to a list of dictionaries with 'date' + data = [{'date': date, 'fac_level': inner_dict_key, 'value': inner_dict_set} for + inner_dict_key, inner_dict_set in inner_dict.items()] + # Create a DataFrame from the list with date & fac_level as indexes + temp_df = pd.DataFrame(data) + temp_df.set_index(['date', 'fac_level'], inplace=True) + temp_df.columns = [None] + + # Concatenate the temporary DataFrame to the result DataFrame + df = pd.concat([df, temp_df]) + + # print(f"\ndf\n {df}") + df.columns = [None] + + return df + + return _df \ + .set_index('date') \ + .pipe(unpack_dict_in_series) \ + .stack() \ + .droplevel(level=2) + + return extract_results( + results_folder, + module='tlo.methods.healthsystem.summary', + key='Equipment', + custom_generate_series=get_equipment_declaration_by_levels + ) + + +def create_equipment_catalogue(results_folder: Path, output_folder: Path): + # Declare path for output file from this script + output_file_name = 'equipment_catalogue_by_level.csv' + output_detailed_file_name = 'equipment_catalogue_by_date_level_sim.csv' + + sim_equipment = get_annual_equipment_declarations_by_levels(results_folder) + sim_equipment_df = pd.DataFrame(sim_equipment) + sim_equipment_df.index.names = ['date', 'fac_level'] + + # Save the detailed CSV + sim_equipment_df.to_csv(output_folder / output_detailed_file_name) + print('equipment_catalogue_by_date_level_sim.csv saved.') + + # Prepare a catalogue only by facility levels + # Define a custom aggregation function to combine sets in columns for each row + def combine_sets(row): + combined_set = set() + for col in row: + combined_set.update(col) + return combined_set + + # Apply the custom aggregation function to each row + sim_equipment_by_level_df = sim_equipment_df.copy() + sim_equipment_by_level_df['equipment'] = sim_equipment_by_level_df.apply(combine_sets, axis=1) + # Group by 'fac_level' and join rows with the same 'fac_level' into one set + sim_equipment_by_level_df.reset_index(inplace=True) + sim_equipment_by_level_df = sim_equipment_by_level_df.groupby('fac_level')['equipment'].apply( + lambda x: list(set.union(*x)) + ).reset_index() + + # Explode the 'equipment' column to separate elements into rows + sim_equipment_by_level_df = sim_equipment_by_level_df.explode('equipment', ignore_index=True).set_index('fac_level') + + # Save the CSV equipment catalogue + sim_equipment_by_level_df.to_csv(output_folder / output_file_name) + print('equipment_catalogue_by_level.csv saved.') + + return 0 + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("results_folder", type=Path) + args = parser.parse_args() + + create_equipment_catalogue( + results_folder=args.results_folder, + output_folder=args.results_folder, + ) +# NB. Edit run configuration, the Parameters: "./outputs/sejjej5@ucl.ac.uk/long_run_all_diseases-2023-09-04T233551Z" diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index f3fc29108e..a741eefee3 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -282,6 +282,9 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series: # get number of draws and numbers of runs info = get_scenario_info(results_folder) + def is_number(element): + return isinstance(element, (int, float)) + # Collect results from each draw/run res = dict() for draw in range(info['number_of_draws']): @@ -293,7 +296,10 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series: df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key] output_from_eval: pd.Series = generate_series(df) assert pd.Series == type(output_from_eval), 'Custom command does not generate a pd.Series' - res[draw_run] = output_from_eval * get_multiplier(draw, run) + if output_from_eval.apply(is_number).all(): + res[draw_run] = output_from_eval * get_multiplier(draw, run) + else: + res[draw_run] = output_from_eval except KeyError: # Some logs could not be found - probably because this run failed. From 0ff003b511901f93026632844ac7829c128c534f Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 7 Sep 2023 17:12:45 +0100 Subject: [PATCH 337/443] equipment_catalogue: PEP8 --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index c88a469c2f..fdaca54515 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -1,8 +1,9 @@ import argparse -import pandas as pd from pathlib import Path + +import pandas as pd + from tlo.analysis.utils import extract_results -from functools import reduce def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: From f73693913dc226c2bf17dab822325047bbc71681 Mon Sep 17 00:00:00 2001 From: Eva Janouskova <48157464+EvaJanouskova@users.noreply.github.com> Date: Tue, 12 Sep 2023 19:53:10 +0200 Subject: [PATCH 338/443] healthsystem: sort equipment for log Co-authored-by: Tim Hallett <39991060+tbhallett@users.noreply.github.com> --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 4fd39fcb25..1a07595d9e 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2834,7 +2834,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": self._equip_by_level, + "Equipment_By_Level": sorted(self._equip_by_level), }, ) From 745d4aba4eae181da569f49b4e6cd99bfa012e28 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 18:26:35 +0100 Subject: [PATCH 339/443] equipment_catalogue: comment updated --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index fdaca54515..ecbc206aba 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -8,7 +8,8 @@ def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated - year.""" + year. + NB. healthsystem.summary logger required to have been set at the level INFO or higher.""" def get_equipment_declaration_by_levels(_df): """Get the equipment declaration by facility levels for the year.""" From e7c52d82136be4457938ab7845fd3d11712f14bb Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:10:01 +0100 Subject: [PATCH 340/443] rti: unified use of consumables/equipment terms --- src/tlo/methods/rti.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 0d788aaf6c..f92d751e14 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4664,9 +4664,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe @@ -5002,7 +5000,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # equipment used by surgeon, gloves and facemask + # consumables used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe From b523d584d1134f7b7a6fc71833ef2f2644a8579a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 12 Sep 2023 19:15:44 +0100 Subject: [PATCH 341/443] hs, brc, co: used_equipment renamed to EQUIPMENT; if equip always same for HSI - set in __init__, otherwise updated in apply diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 5d8fabfcb..26155729a 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -646,6 +646,8 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -666,8 +668,6 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -761,8 +761,9 @@ class HSI_BreastCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Record used equipment - self.used_equipment = {'Anything used for mastectomy as I guess this is about'} + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git src/tlo/methods/contraception.py src/tlo/methods/contraception.py index 6ffb0ebc6..15851e1b7 100644 --- src/tlo/methods/contraception.py +++ src/tlo/methods/contraception.py @@ -1281,11 +1281,12 @@ class HSI_Contraception_FamilyPlanningAppt(HSI_Event, IndividualScopeEventMixin) _new_contraceptive = self.new_contraceptive - # Record used equipment when needed + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': - self.used_equipment = {'Smt used to sterilize a woman'} + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': - self.used_equipment = {'Equipment used when performing IUD'} + self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: _new_contraceptive = "not_using" diff --git src/tlo/methods/healthsystem.py src/tlo/methods/healthsystem.py index c19b0f433..3eb6b9940 100644 --- src/tlo/methods/healthsystem.py +++ src/tlo/methods/healthsystem.py @@ -182,7 +182,7 @@ class HSI_Event: self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - self.used_equipment = set() + self.EQUIPMENT = set() @property def bed_days_allocated_to_this_event(self): @@ -1741,7 +1741,7 @@ class HealthSystem(Module): squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.used_equipment, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( --- src/tlo/methods/breast_cancer.py | 9 +++++---- src/tlo/methods/contraception.py | 7 ++++--- src/tlo/methods/healthsystem.py | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 5d8fabfcb2..26155729ab 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,6 +646,8 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} + # biopsy always performed with this HSI def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -666,8 +668,6 @@ def apply(self, person_id, squeeze_factor): # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this - self.used_equipment = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy', - 'Mammograph maybe?'} dx_result = hs.dx_manager.run_dx_test( dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible', @@ -761,8 +761,9 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Record used equipment - self.used_equipment = {'Anything used for mastectomy as I guess this is about'} + # Update equipment used with treatment + # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher + self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 4def5a649d..1d4d62a929 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1196,11 +1196,12 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Record used equipment when needed + # Update equipment when needed + # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher if _new_contraceptive == 'female_sterilization': - self.used_equipment = {'Smt used to sterilize a woman'} + self.EQUIPMENT.update({'Smt used to sterilize a woman'}) elif _new_contraceptive == 'IUD': - self.used_equipment = {'Equipment used when performing IUD'} + self.EQUIPMENT.update({'Equipment used when performing IUD'}) else: _new_contraceptive = "not_using" diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1a07595d9e..39654911c6 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -182,7 +182,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - self.used_equipment = set() + self.EQUIPMENT = set() @property def bed_days_allocated_to_this_event(self): @@ -1811,7 +1811,7 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.used_equipment, + equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( From 539626928ec782ac5ebfe251ac18b0e09dbbb5c6 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 18 Sep 2023 17:33:16 +0100 Subject: [PATCH 342/443] brc: comment updated diff --git src/tlo/methods/breast_cancer.py src/tlo/methods/breast_cancer.py index 26155729a..aef476c87 100644 --- src/tlo/methods/breast_cancer.py +++ src/tlo/methods/breast_cancer.py @@ -647,7 +647,7 @@ class HSI_BreastCancer_Investigation_Following_breast_lump_discernible(HSI_Event self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 26155729ab..aef476c870 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -647,7 +647,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI + # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 2791c18cec1bb562e57dba8ed6edef01e03009cb Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 20 Sep 2023 17:44:03 +0100 Subject: [PATCH 343/443] brc & co: rm the dummy examples of equipment from modules --- src/tlo/methods/breast_cancer.py | 6 ------ src/tlo/methods/contraception.py | 10 +--------- 2 files changed, 1 insertion(+), 15 deletions(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index aef476c870..1ce9ad2bf6 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,8 +646,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - self.EQUIPMENT = {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - # biopsy always performed with this HSI, hence always used the same set of equipment def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -761,10 +759,6 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # Update equipment used with treatment - # NB. read only with HSI run and healthsystem.summary logger set at the level INFO or higher - self.EQUIPMENT.update({'Anything used for mastectomy as I guess this is about'}) - # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 1d4d62a929..0a4884d0f3 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1170,8 +1170,7 @@ 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 and record used equipment if any, if not set the contraception to - # "not_using": + # if so do log the availability of all items, 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: @@ -1196,13 +1195,6 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Update equipment when needed - # NB. read only with HSI run and healthsystem.summary logger set at the level of logger.INFO or higher - if _new_contraceptive == 'female_sterilization': - self.EQUIPMENT.update({'Smt used to sterilize a woman'}) - elif _new_contraceptive == 'IUD': - self.EQUIPMENT.update({'Equipment used when performing IUD'}) - else: _new_contraceptive = "not_using" From 07e1ae92321797f3451e4cb089619b0f816ffb3a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 24 Sep 2023 16:15:42 +0100 Subject: [PATCH 344/443] RF_Equipment: equipment catalogue - first draft (from Sakshi) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv new file mode 100644 index 0000000000..b119a1d503 --- /dev/null +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17a3a35a9c7908c9a291b8906ae1f8f7be6ff89459a183900de142ee29368c53 +size 36731 From 00270c19507deb4a8cdcf686e7e6139ae11c0dd6 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 24 Sep 2023 23:54:02 +0100 Subject: [PATCH 345/443] RF_Equipment: equipment catalogue - merge duplicates (round 1) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index b119a1d503..4e936f4cb9 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17a3a35a9c7908c9a291b8906ae1f8f7be6ff89459a183900de142ee29368c53 -size 36731 +oid sha256:faf0337415b4ef87fc47dace921c9f9fb4450d88ff1718def0a96e80fa073cb6 +size 34520 From d486c4da2c6f56053db3e492e8c393ae51b891e7 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 09:56:16 +0100 Subject: [PATCH 346/443] RF_Equipment: equipment catalogue - merge duplicates (round 2) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 4e936f4cb9..22f62d5a0f 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:faf0337415b4ef87fc47dace921c9f9fb4450d88ff1718def0a96e80fa073cb6 -size 34520 +oid sha256:b4f78c47032a3e658fc1d07fd631c2e8aea85b145a7978fcd5d965bf9e0c5fce +size 32543 From 860d9524d0f904271a4efee31a256a9d24d88e77 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 21:50:20 +0100 Subject: [PATCH 347/443] hs: debugging Equipment log - rm sorted --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 39654911c6..ab4bdd0ec1 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2834,7 +2834,7 @@ def write_to_log_and_reset_counters(self): key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", data={ - "Equipment_By_Level": sorted(self._equip_by_level), + "Equipment_By_Level": self._equip_by_level, }, ) From c573fc8550b0549e885658ae31f100c781501554 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 28 Sep 2023 00:49:34 +0100 Subject: [PATCH 348/443] hs: fix sorting of _equip_by_level --- src/tlo/methods/healthsystem.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index ab4bdd0ec1..bdddc1d76a 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2830,6 +2830,9 @@ def write_to_log_and_reset_counters(self): }, ) + # Sort equipment within levels, and log them + for key in self._equip_by_level: + self._equip_by_level[key] = sorted(self._equip_by_level[key]) logger_summary.info( key="Equipment", description="Sets of used equipment for each facility level in this calendar year.", From 373018c63ddf79aa68d1992755c9c5355c0e683a Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 27 Sep 2023 19:08:35 +0100 Subject: [PATCH 349/443] RF_Equipment: equipment catalogue - merge duplicates (round 3) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 22f62d5a0f..6178c242ba 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b4f78c47032a3e658fc1d07fd631c2e8aea85b145a7978fcd5d965bf9e0c5fce -size 32543 +oid sha256:9f02f434986e8070dacc50405e2b88826be18eb6de61b4e6475e7a4cad948334 +size 32455 From 307efab9a37fe18442578438886c53cc69627f04 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 28 Sep 2023 00:32:23 +0100 Subject: [PATCH 350/443] RF_Equipment: equipment catalogue - merge duplicates (round 4) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 6178c242ba..aca3040b03 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9f02f434986e8070dacc50405e2b88826be18eb6de61b4e6475e7a4cad948334 -size 32455 +oid sha256:29782fdacd7b13efef9b0b9de23fb7413b329bb9880b87c3b0522e6732130bc1 +size 31872 From 4f2ce6c7775f614d272a787664a37fa364bff73d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 29 Sep 2023 10:45:25 +0100 Subject: [PATCH 351/443] RF_Equipment: equipment catalogue - merge duplicates (round 5) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index aca3040b03..0055cd8d8e 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:29782fdacd7b13efef9b0b9de23fb7413b329bb9880b87c3b0522e6732130bc1 -size 31872 +oid sha256:f3854096278164e1e3c63c2aa2e7afecbae1c6ee14dabcff829af78c271cd82d +size 31712 From 10b9dde4738dc38df76a666bc3924c6552ee9257 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:09:49 +0000 Subject: [PATCH 352/443] RF_Equipment: equipment catalogue - merge duplicates (round 6) + col names renamed --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 0055cd8d8e..c7f2e7e240 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f3854096278164e1e3c63c2aa2e7afecbae1c6ee14dabcff829af78c271cd82d -size 31712 +oid sha256:c1f647adbbd18cbc1460135450d2f0b402fac00f80f050fcb3cd8c138b822f71 +size 31663 From 1f2ff79d5d29c308e21338fe2c245236030dd1d0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:23:41 +0000 Subject: [PATCH 353/443] RF_Equip: equip item codes added --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index c7f2e7e240..f0a2b53c9f 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c1f647adbbd18cbc1460135450d2f0b402fac00f80f050fcb3cd8c138b822f71 -size 31663 +oid sha256:cd2c3558a4d30dfb6d913054513e1d6df1b91f4955b60371e762eff05ac99afd +size 32759 From adde374490047539cd19b4768dcc7bda5fc829de Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:21:55 +0000 Subject: [PATCH 354/443] codes_to_items_list: new script created --- src/tlo/analysis/codes_to_items_list.py | 70 +++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 src/tlo/analysis/codes_to_items_list.py diff --git a/src/tlo/analysis/codes_to_items_list.py b/src/tlo/analysis/codes_to_items_list.py new file mode 100644 index 0000000000..365f2e64b8 --- /dev/null +++ b/src/tlo/analysis/codes_to_items_list.py @@ -0,0 +1,70 @@ +""" +(1) Can be used for a list of items without item codes yet saved in a csv file named 'csv_file_to_update_name'. + +This script will assign unique code to each unique item name which has no code assigned yet. The codes are +assigned in order from the sequence 0, 1, 2, .... + +Duplicated items are allowed, the same code will be assigned to the same items. + +(2) Can be used when new items are added later without item codes but some items with codes are already in the list. + +This script will keep the existing codes for items with already assigned code and for items without existing +code will assign new code (continue in sequence, i.e. if the highest code is 5, it assigns new codes from the continuing +sequence 6, 7, 8, ...). + +------ +NB. Make sure the 'csv_file_to_update_name' is the file you want to update. The output will be named +'csv_file_to_update_name' + '_new.csv' to avoid unintentionally losing the previous version. +------ +""" + +import pandas as pd +from pathlib import Path + + +# ## CHANGE THIS IF YOU WANT TO USE DIFFERENT FILE AS INPUT +csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' + +# Get the path of the current script file +script_path = Path(__file__) +print(script_path) + +# Specify the file path to RF csv file +file_path = script_path.parent.parent.parent.parent / 'resources/healthsystem/infrastructure_and_equipment' + +# Load the CSV RF into a DataFrame +df = pd.read_csv(Path(file_path) / str(csv_file_to_update_name + '.csv')) + +# Find unique values in Equipment that have no code and are not None or empty +unique_values =\ + df.loc[df['Equip_Code'].isna() & df['Equip_Item'].notna() & (df['Equip_Item'] != ''), 'Equip_Item'].unique() + +# Create a mapping of unique values to codes +value_to_code = {} +# Initialize the starting code value +if not df['Equip_Code'].isna().all(): + next_code = int(df['Equip_Code'].max()) + 1 +else: + next_code = 0 + +# Iterate through unique values +for value in unique_values: + # Check if there is at least one existing code for this value + matching_rows = df.loc[df['Equip_Item'] == value, 'Equip_Code'].dropna() + if not matching_rows.empty: + # Use the existing code for this value + existing_code = int(matching_rows.iloc[0]) + # TODO: verify all the codes are the same + else: + # If no existing codes, start with the next available code + existing_code = next_code + next_code += 1 + value_to_code[value] = existing_code + # Update the 'Equip_Code' column for matching rows + df.loc[df['Equip_Item'] == value, 'Equip_Code'] = existing_code + +# Convert 'Equip_Code' column to integers +df['Equip_Code'] = df['Equip_Code'].astype('Int64') # Convert to nullable integer type + +# Save CSV with equipment codes +df.to_csv(Path(file_path) / str(csv_file_to_update_name + '_new.csv'), index=False) From e6013d9540dcb16b9934abcc490c6f827d89a971 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 15 Nov 2023 18:45:42 +0000 Subject: [PATCH 355/443] codes_to_items_list: script generalised + PEP 8 --- src/tlo/analysis/codes_to_items_list.py | 31 ++++++++++++++----------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/tlo/analysis/codes_to_items_list.py b/src/tlo/analysis/codes_to_items_list.py index 365f2e64b8..da169a06e5 100644 --- a/src/tlo/analysis/codes_to_items_list.py +++ b/src/tlo/analysis/codes_to_items_list.py @@ -18,39 +18,44 @@ ------ """ -import pandas as pd from pathlib import Path - -# ## CHANGE THIS IF YOU WANT TO USE DIFFERENT FILE AS INPUT -csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' +import pandas as pd # Get the path of the current script file script_path = Path(__file__) print(script_path) -# Specify the file path to RF csv file +# ############################# +# ## CHANGE THIS FOR YOUR FILE +# Specify name of the csv file +csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' +# Specify the file path to csv file file_path = script_path.parent.parent.parent.parent / 'resources/healthsystem/infrastructure_and_equipment' +# Specify the names of columns containing the item names and item codes +item_col_name = 'Equip_Item' +code_col_name = 'Equip_Code' +# ############################# # Load the CSV RF into a DataFrame df = pd.read_csv(Path(file_path) / str(csv_file_to_update_name + '.csv')) # Find unique values in Equipment that have no code and are not None or empty unique_values =\ - df.loc[df['Equip_Code'].isna() & df['Equip_Item'].notna() & (df['Equip_Item'] != ''), 'Equip_Item'].unique() + df.loc[df[code_col_name].isna() & df[item_col_name].notna() & (df[item_col_name] != ''), item_col_name].unique() # Create a mapping of unique values to codes value_to_code = {} # Initialize the starting code value -if not df['Equip_Code'].isna().all(): - next_code = int(df['Equip_Code'].max()) + 1 +if not df[code_col_name].isna().all(): + next_code = int(df[code_col_name].max()) + 1 else: next_code = 0 # Iterate through unique values for value in unique_values: # Check if there is at least one existing code for this value - matching_rows = df.loc[df['Equip_Item'] == value, 'Equip_Code'].dropna() + matching_rows = df.loc[df[item_col_name] == value, code_col_name].dropna() if not matching_rows.empty: # Use the existing code for this value existing_code = int(matching_rows.iloc[0]) @@ -60,11 +65,11 @@ existing_code = next_code next_code += 1 value_to_code[value] = existing_code - # Update the 'Equip_Code' column for matching rows - df.loc[df['Equip_Item'] == value, 'Equip_Code'] = existing_code + # Update the code_col_name column for matching rows + df.loc[df[item_col_name] == value, code_col_name] = existing_code -# Convert 'Equip_Code' column to integers -df['Equip_Code'] = df['Equip_Code'].astype('Int64') # Convert to nullable integer type +# Convert code_col_name column to integers +df[code_col_name] = df[code_col_name].astype('Int64') # Convert to nullable integer type # Save CSV with equipment codes df.to_csv(Path(file_path) / str(csv_file_to_update_name + '_new.csv'), index=False) From a214a888158b263d24d9fcd1ca7752c99e10a67d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 16 Nov 2023 17:55:35 +0000 Subject: [PATCH 356/443] hs: equipment added to HSIEventDetails --- src/tlo/methods/healthsystem.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index bdddc1d76a..fe254fbadc 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -126,6 +126,7 @@ class HSIEventDetails(NamedTuple): facility_level: Optional[str] appt_footprint: Tuple[Tuple[str, int]] beddays_footprint: Tuple[Tuple[str, int]] + equipment: set class HSIEventQueueItem(NamedTuple): @@ -399,7 +400,8 @@ def as_namedtuple( appt_footprint=tuple(sorted(appt_footprint.items())), beddays_footprint=tuple( sorted((k, v) for k, v in self.BEDDAYS_FOOTPRINT.items() if v > 0) - ) + ), + equipment=(tuple(self.EQUIPMENT)) ) @@ -1837,10 +1839,13 @@ def write_to_hsi_log( 'did_run': did_run, 'Facility_Level': event_details.facility_level if event_details.facility_level is not None else -99, 'Facility_ID': facility_id if facility_id is not None else -99, + 'equipment': equipment, }, description="record of each HSI event" ) if did_run: + print("\nevent_details") + print(event_details) if self._hsi_event_count_log_period is not None: event_details_key = self._hsi_event_details.setdefault( event_details, len(self._hsi_event_details) @@ -1885,7 +1890,7 @@ def write_to_never_ran_hsi_log( event_details: HSIEventDetails, person_id: int, facility_id: Optional[int], - priority: int, + priority: int ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1910,7 +1915,7 @@ def write_to_never_ran_hsi_log( treatment_id=event_details.treatment_id, hsi_event_name=event_details.event_name, appt_footprint=event_details.appt_footprint, - level=event_details.facility_level, + level=event_details.facility_level ) def log_current_capabilities_and_usage(self): @@ -2170,7 +2175,7 @@ def run_individual_level_events_in_mode_0_or_1(self, actual_appt_footprint=actual_appt_footprint, squeeze_factor=squeeze_factor, did_run=True, - priority=_priority + priority=_priority, ) # if not ok_to_run @@ -2668,7 +2673,8 @@ def apply(self, population): treatment_id='Inpatient_Care', facility_level=self.module._facility_by_facility_id[_fac_id].level, appt_footprint=tuple(sorted(_inpatient_appts.items())), - beddays_footprint=() + beddays_footprint=(), + equipment=() # TODO: what should be in here? ), person_id=-1, facility_id=_fac_id, From 32145b6f373dba22a70ba60c0b1cc8eca4d23455 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 17 Nov 2023 18:51:25 +0000 Subject: [PATCH 357/443] equip_catalogue: make catalogues from new logging (equip included in hsi_event_details, counts logged in hsi_event_counts) --- .../equipment/equipment_catalogue.py | 117 +++++++++++------- 1 file changed, 69 insertions(+), 48 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index ecbc206aba..9672c92474 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -3,36 +3,35 @@ import pandas as pd -from tlo.analysis.utils import extract_results +from tlo.analysis.utils import extract_results, load_pickled_dataframes -def get_annual_equipment_declarations_by_levels(results_folder: Path) -> pd.DataFrame: - """Return pd.DataFrame gives the simulated annual equipment declaration by facility levels for each simulated - year. - NB. healthsystem.summary logger required to have been set at the level INFO or higher.""" +def get_annual_hsi_event_counts(results_folder: Path) -> pd.DataFrame: + """Return pd.DataFrame gives the simulated annual counts of all the hsi event details logged (details as keys) + for each simulated year. + NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" - def get_equipment_declaration_by_levels(_df): - """Get the equipment declaration by facility levels for the year.""" + def get_hsi_event_counts(_df): + """Get the counts of all the hsi event details logged.""" def unpack_dict_in_series(_raw: pd.Series): # Create an empty DataFrame to store the data df = pd.DataFrame() # Iterate through the dictionary items - for col_name, mydict in _raw.items(): + for _, mydict in _raw.items(): for date, inner_dict in mydict.items(): # Convert the inner_dict to a list of dictionaries with 'date' - data = [{'date': date, 'fac_level': inner_dict_key, 'value': inner_dict_set} for + data = [{'date': date, 'event_details_key': inner_dict_key, 'count': inner_dict_set} for inner_dict_key, inner_dict_set in inner_dict.items()] # Create a DataFrame from the list with date & fac_level as indexes temp_df = pd.DataFrame(data) - temp_df.set_index(['date', 'fac_level'], inplace=True) + temp_df.set_index(['date', 'event_details_key'], inplace=True) temp_df.columns = [None] # Concatenate the temporary DataFrame to the result DataFrame df = pd.concat([df, temp_df]) - # print(f"\ndf\n {df}") df.columns = [None] return df @@ -46,47 +45,69 @@ def unpack_dict_in_series(_raw: pd.Series): return extract_results( results_folder, module='tlo.methods.healthsystem.summary', - key='Equipment', - custom_generate_series=get_equipment_declaration_by_levels + key='hsi_event_counts', + custom_generate_series=get_hsi_event_counts ) -def create_equipment_catalogue(results_folder: Path, output_folder: Path): - # Declare path for output file from this script - output_file_name = 'equipment_catalogue_by_level.csv' - output_detailed_file_name = 'equipment_catalogue_by_date_level_sim.csv' +def get_hsi_event_keys(results_folder: Path): + return load_pickled_dataframes(results_folder, 0, 0, "tlo.methods.healthsystem.summary")[ + "tlo.methods.healthsystem.summary" + ]["hsi_event_details"]["hsi_event_key_to_event_details"][0] - sim_equipment = get_annual_equipment_declarations_by_levels(results_folder) + +def create_equipment_catalogues(results_folder: Path, output_folder: Path): + + # Declare output file names + output_detailed_file_name = 'equipment_annual_counts__all_event_details.csv' + output_file_name = 'equipment_annual_counts__by_Date_EventName_FacLevel.csv' + + # %% Catalogue equipment by all HSI event details + sim_equipment = get_annual_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) - sim_equipment_df.index.names = ['date', 'fac_level'] - - # Save the detailed CSV - sim_equipment_df.to_csv(output_folder / output_detailed_file_name) - print('equipment_catalogue_by_date_level_sim.csv saved.') - - # Prepare a catalogue only by facility levels - # Define a custom aggregation function to combine sets in columns for each row - def combine_sets(row): - combined_set = set() - for col in row: - combined_set.update(col) - return combined_set - - # Apply the custom aggregation function to each row - sim_equipment_by_level_df = sim_equipment_df.copy() - sim_equipment_by_level_df['equipment'] = sim_equipment_by_level_df.apply(combine_sets, axis=1) - # Group by 'fac_level' and join rows with the same 'fac_level' into one set - sim_equipment_by_level_df.reset_index(inplace=True) - sim_equipment_by_level_df = sim_equipment_by_level_df.groupby('fac_level')['equipment'].apply( - lambda x: list(set.union(*x)) - ).reset_index() - - # Explode the 'equipment' column to separate elements into rows - sim_equipment_by_level_df = sim_equipment_by_level_df.explode('equipment', ignore_index=True).set_index('fac_level') - - # Save the CSV equipment catalogue - sim_equipment_by_level_df.to_csv(output_folder / output_file_name) - print('equipment_catalogue_by_level.csv saved.') + sim_equipment_df.fillna(0, inplace=True) + + hsi_event_keys = get_hsi_event_keys(results_folder) + + decoded_keys = sim_equipment_df.index.get_level_values(1).astype(str).map(hsi_event_keys) + sim_equipment_df = pd.concat([sim_equipment_df, pd.DataFrame(decoded_keys.tolist(), index=sim_equipment_df.index)], axis=1) + # Make values in 'appt_footprint', and 'beddays_footprint' columns to be string + sim_equipment_df['appt_footprint'] = sim_equipment_df['appt_footprint'].apply(lambda x: ', '.join(map(str, x))) + sim_equipment_df['beddays_footprint'] = sim_equipment_df['beddays_footprint'].apply(lambda x: ', '.join(map(str, x))) + # Explode the 'equipment' column + exploded_df = sim_equipment_df.explode('equipment') + # Remove the 'event_details_key' and replace the index with hsi event details as indexes + exploded_df = exploded_df.droplevel(level=1) + exploded_df = exploded_df.set_index( + ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', + 'equipment'], append=True + ) + # Sum values with the same multi-index + exploded_df = exploded_df.groupby(exploded_df.index).sum() + # Convert the index back to a MultiIndex + exploded_df.index = pd.MultiIndex.from_tuples(exploded_df.index) + exploded_df.index.names = \ + ['date', 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', + 'equipment'] + + # Save the detailed equipment catalogue by levels + exploded_df.to_csv(output_folder / output_detailed_file_name) + print(f'{output_detailed_file_name} saved.') + # --- + + # %% Catalogue equipment by Facility Levels and HSI Event Names + + equipment_counts_by_date_hsi_name_level_df = exploded_df.copy() + + # Sum counts for each equipment with the same date, hsi event name, and level (remaining indexes removed) + # equipment_counts_by_date_hsi_name_level_df + equipment_counts_by_date_hsi_name_level_df = \ + equipment_counts_by_date_hsi_name_level_df.groupby(['date', 'event_name', 'facility_level', 'equipment']).sum() + + # Save the CSV equipment counts + equipment_counts_by_date_hsi_name_level_df.to_csv(output_folder / output_file_name) + print(f'{output_file_name} saved.') + # --- return 0 @@ -96,7 +117,7 @@ def combine_sets(row): parser.add_argument("results_folder", type=Path) args = parser.parse_args() - create_equipment_catalogue( + create_equipment_catalogues( results_folder=args.results_folder, output_folder=args.results_folder, ) From d8e299f6445e394e54da6b1d744d1f8b6371c643 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 19 Nov 2023 16:44:27 +0000 Subject: [PATCH 358/443] hs: sort equipment for logging --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index fe254fbadc..230a7c1b31 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -401,7 +401,7 @@ def as_namedtuple( beddays_footprint=tuple( sorted((k, v) for k, v in self.BEDDAYS_FOOTPRINT.items() if v > 0) ), - equipment=(tuple(self.EQUIPMENT)) + equipment=(tuple(sorted(self.EQUIPMENT))) ) From 598368d84a8942073b49f56e655dba4b892a0488 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 19 Nov 2023 23:58:02 +0000 Subject: [PATCH 359/443] test_hs: assert equipment logging within detailed_hsi_event --- tests/test_healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_healthsystem.py b/tests/test_healthsystem.py index e07a2d5889..22b9d7ff8c 100644 --- a/tests/test_healthsystem.py +++ b/tests/test_healthsystem.py @@ -862,7 +862,7 @@ def apply(self, person_id, squeeze_factor): detailed_consumables = log["tlo.methods.healthsystem"]['Consumables'] assert {'date', 'TREATMENT_ID', 'did_run', 'Squeeze_Factor', 'priority', 'Number_By_Appt_Type_Code', 'Person_ID', - 'Facility_Level', 'Facility_ID', 'Event_Name', + 'Facility_Level', 'Facility_ID', 'Event_Name', 'equipment' } == set(detailed_hsi_event.columns) assert {'date', 'Frac_Time_Used_Overall', 'Frac_Time_Used_By_Facility_ID', 'Frac_Time_Used_By_OfficerType', } == set(detailed_capacity.columns) From e2fdafb8a5c88509fe85396e92720ec1dc4f6985 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 23 Nov 2023 12:00:46 +0000 Subject: [PATCH 360/443] [no ci] hs: typo; Equipment logging removed diff --git src/tlo/methods/healthsystem.py src/tlo/methods/healthsystem.py index d14f8f10a..5ecb43368 100644 --- src/tlo/methods/healthsystem.py +++ src/tlo/methods/healthsystem.py @@ -2611,7 +2611,7 @@ class HealthSystemScheduler(RegularEvent, PopulationScopeEventMixin): squeeze_factor=0.0, priority=-1, did_run=True, - equipment=set() # TODO: explore more, should it be non-emtpy in some cases? + equipment=set(), # TODO: explore more, should it be non-empty in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2666,7 +2666,6 @@ class HealthSystemSummaryCounter: self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} # <--Same as `self._appts` but also split by facility_level - self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran @@ -2683,8 +2682,7 @@ class HealthSystemSummaryCounter: hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str, - equipment: set + level: str ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2701,9 +2699,6 @@ class HealthSystemSummaryCounter: self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number - # Update used equipment by level - self._equip_by_level[level].update(equipment) - def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2766,17 +2761,6 @@ class HealthSystemSummaryCounter: }, ) - # Sort equipment within levels, and log them - for key in self._equip_by_level: - self._equip_by_level[key] = sorted(self._equip_by_level[key]) - logger_summary.info( - key="Equipment", - description="Sets of used equipment for each facility level in this calendar year.", - data={ - "Equipment_By_Level": self._equip_by_level, - }, - ) - self._reset_internal_stores() --- src/tlo/methods/healthsystem.py | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 230a7c1b31..af0610fe6c 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2681,7 +2681,7 @@ def apply(self, population): squeeze_factor=0.0, priority=-1, did_run=True, - equipment=set() # TODO: explore more, should it be non-emtpy in some cases? + equipment=set(), # TODO: explore more, should it be non-empty in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. @@ -2736,7 +2736,6 @@ def _reset_internal_stores(self) -> None: self._appts = defaultdict(int) # Running record of the Appointments of `HSI_Event`s that have run self._appts_by_level = {_level: defaultdict(int) for _level in ('0', '1a', '1b', '2', '3', '4')} # <--Same as `self._appts` but also split by facility_level - self._equip_by_level = {_level: set() for _level in ('0', '1a', '1b', '2', '3', '4')} # Log HSI_Events that never ran to monitor shortcoming of Health System self._never_ran_treatment_ids = defaultdict(int) # As above, but for `HSI_Event`s that never ran @@ -2753,8 +2752,7 @@ def record_hsi_event(self, hsi_event_name: str, squeeze_factor: float, appt_footprint: Counter, - level: str, - equipment: set + level: str ) -> None: """Add information about an `HSI_Event` to the running summaries.""" @@ -2771,9 +2769,6 @@ def record_hsi_event(self, self._appts[appt_type] += number self._appts_by_level[level][appt_type] += number - # Update used equipment by level - self._equip_by_level[level].update(equipment) - def record_never_ran_hsi_event(self, treatment_id: str, hsi_event_name: str, @@ -2836,17 +2831,6 @@ def write_to_log_and_reset_counters(self): }, ) - # Sort equipment within levels, and log them - for key in self._equip_by_level: - self._equip_by_level[key] = sorted(self._equip_by_level[key]) - logger_summary.info( - key="Equipment", - description="Sets of used equipment for each facility level in this calendar year.", - data={ - "Equipment_By_Level": self._equip_by_level, - }, - ) - self._reset_internal_stores() From acf3a6361fbecfdcf1ca4bcbf55051f4bd64d562 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Dec 2023 23:16:05 +0000 Subject: [PATCH 361/443] equip_catalogue: fix the keys mapping to be done for each run --- .../equipment/equipment_catalogue.py | 157 ++++++++++++------ 1 file changed, 104 insertions(+), 53 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 9672c92474..1ba0f83cb4 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -1,14 +1,23 @@ import argparse +import warnings from pathlib import Path import pandas as pd -from tlo.analysis.utils import extract_results, load_pickled_dataframes +from tlo.analysis.utils import extract_results +# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# Declare whether to scale the counts to Malawi population size +do_scaling = True +# Declare output file names +output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' +output_file_name = 'equipment_annual_counts__by_Year_TreatmentID_FacLevel.csv' +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -def get_annual_hsi_event_counts(results_folder: Path) -> pd.DataFrame: - """Return pd.DataFrame gives the simulated annual counts of all the hsi event details logged (details as keys) - for each simulated year. + +def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: + """Returned pd.DataFrame gives the monthly counts of all the hsi event details logged (details as keys) + for each simulated month. NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" def get_hsi_event_counts(_df): @@ -46,68 +55,110 @@ def unpack_dict_in_series(_raw: pd.Series): results_folder, module='tlo.methods.healthsystem.summary', key='hsi_event_counts', - custom_generate_series=get_hsi_event_counts + custom_generate_series=get_hsi_event_counts, + do_scaling=do_scaling ) -def get_hsi_event_keys(results_folder: Path): - return load_pickled_dataframes(results_folder, 0, 0, "tlo.methods.healthsystem.summary")[ - "tlo.methods.healthsystem.summary" - ]["hsi_event_details"]["hsi_event_key_to_event_details"][0] +def get_hsi_event_keys_all_runs(results_folder: Path) -> pd.DataFrame: + """Returned pd.DataFrame gives the dictionaries of hsi_event_details for each draw and run. + NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" + def get_hsi_event_keys(_df): + """Get the hsi_event_keys for one particular run.""" + return _df['hsi_event_key_to_event_details'] -def create_equipment_catalogues(results_folder: Path, output_folder: Path): + return extract_results( + results_folder, + module='tlo.methods.healthsystem.summary', + key='hsi_event_details', + custom_generate_series=get_hsi_event_keys + ) - # Declare output file names - output_detailed_file_name = 'equipment_annual_counts__all_event_details.csv' - output_file_name = 'equipment_annual_counts__by_Date_EventName_FacLevel.csv' + +def create_equipment_catalogues(results_folder: Path, output_folder: Path): # %% Catalogue equipment by all HSI event details - sim_equipment = get_annual_hsi_event_counts(results_folder) + sim_equipment = get_monthly_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) - sim_equipment_df.fillna(0, inplace=True) - - hsi_event_keys = get_hsi_event_keys(results_folder) - - decoded_keys = sim_equipment_df.index.get_level_values(1).astype(str).map(hsi_event_keys) - sim_equipment_df = pd.concat([sim_equipment_df, pd.DataFrame(decoded_keys.tolist(), index=sim_equipment_df.index)], axis=1) - # Make values in 'appt_footprint', and 'beddays_footprint' columns to be string - sim_equipment_df['appt_footprint'] = sim_equipment_df['appt_footprint'].apply(lambda x: ', '.join(map(str, x))) - sim_equipment_df['beddays_footprint'] = sim_equipment_df['beddays_footprint'].apply(lambda x: ', '.join(map(str, x))) - # Explode the 'equipment' column - exploded_df = sim_equipment_df.explode('equipment') - # Remove the 'event_details_key' and replace the index with hsi event details as indexes - exploded_df = exploded_df.droplevel(level=1) - exploded_df = exploded_df.set_index( - ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', - 'equipment'], append=True - ) - # Sum values with the same multi-index - exploded_df = exploded_df.groupby(exploded_df.index).sum() - # Convert the index back to a MultiIndex - exploded_df.index = pd.MultiIndex.from_tuples(exploded_df.index) - exploded_df.index.names = \ - ['date', 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', - 'equipment'] - - # Save the detailed equipment catalogue by levels - exploded_df.to_csv(output_folder / output_detailed_file_name) + hsi_event_keys = get_hsi_event_keys_all_runs(results_folder) + + final_df = pd.DataFrame() + + def details_col_to_str(details_col): + return details_col.apply(lambda x: ', '.join(map(str, x))) + + for col in hsi_event_keys.columns: + df_col = sim_equipment_df[col].dropna() + decoded_keys = df_col.index.get_level_values(1).astype(str).map(hsi_event_keys.at[0, col]) + + # %%% Verify the keys in dictionary and dataframe for the run 'col' are same + # Check if all keys in hsi_event_keys_set are in the 'event_details_key' of df_col + hsi_event_keys_set = set(hsi_event_keys.at[0, col].keys()) + missing_keys_df =\ + [key for key in hsi_event_keys_set if key not in df_col.index.get_level_values('event_details_key')] + + # Check if all keys in the 'event_details_key' of df_col are in hsi_event_keys_set + missing_keys_dict =\ + [key for key in df_col.index.get_level_values('event_details_key') if key not in hsi_event_keys_set] + + # Warn if some keys are missing + if missing_keys_df: + warnings.warn(UserWarning(f"Keys missing in sim_equipment_df for the run {col}: {missing_keys_df}")) + + if missing_keys_dict: + warnings.warn(UserWarning(f"Keys missing in hsi_event_keys for the run {col}: {missing_keys_dict}")) + # %%% + + df_col = pd.concat([df_col, pd.DataFrame(decoded_keys.tolist(), index=df_col.index)], axis=1) + # Make values in 'appt_footprint', 'beddays_footprint' columns to be string + df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) + df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) + # Explode the 'equipment' column + exploded_df = df_col.explode('equipment') + # Remove the 'event_details_key' and replace the index with hsi event details as indexes + exploded_df = exploded_df.droplevel(level=1) + exploded_df = exploded_df.set_index( + ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', + 'equipment'], append=True + ) + # Sum values with the same multi-index (keep also empty indexes) + exploded_df = exploded_df.groupby(level=exploded_df.index.names, dropna=False).sum() + # Add the results for the run 'col' to final_df + final_df = pd.concat([final_df, exploded_df], axis=1) + + # Replace NaN with 0 + final_df.fillna(0, inplace=True) + # Save the detailed equipment catalogue + final_df.to_csv(output_folder / output_detailed_file_name) print(f'{output_detailed_file_name} saved.') # --- - # %% Catalogue equipment by Facility Levels and HSI Event Names - - equipment_counts_by_date_hsi_name_level_df = exploded_df.copy() - - # Sum counts for each equipment with the same date, hsi event name, and level (remaining indexes removed) - # equipment_counts_by_date_hsi_name_level_df - equipment_counts_by_date_hsi_name_level_df = \ - equipment_counts_by_date_hsi_name_level_df.groupby(['date', 'event_name', 'facility_level', 'equipment']).sum() - - # Save the CSV equipment counts - equipment_counts_by_date_hsi_name_level_df.to_csv(output_folder / output_file_name) + # %% Catalogue equipment by Treatment ID and Facility Levels + equipment_counts_by_date_treatment_id_level_df = final_df.copy() + + # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), + # keeping only non-empty 'equipment' indexes + equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( + ['date', 'treatment_id', 'facility_level', 'equipment'], + dropna=True + # TODO: make 'treatment_id', 'facility_level' to be an input + ).sum() + + # Sum counts annually + equipment_counts_by_date_treatment_id_level_df['year'] = \ + equipment_counts_by_date_treatment_id_level_df.index.get_level_values('date').year + # TODO: make annual/monthly results according to input + equipment_counts_by_date_treatment_id_level_df.set_index('year', append=True, inplace=True) + equipment_counts_by_date_treatment_id_level_df.index.droplevel('date') + equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( + ['year', 'treatment_id', 'facility_level', 'equipment'] + ).sum() + + # Save the equipment counts CSV + equipment_counts_by_date_treatment_id_level_df.to_csv(output_folder / output_file_name) print(f'{output_file_name} saved.') - # --- + # # --- return 0 From 97f0f3f8a6cc44f1cbb5007ebec94c1b533e4019 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 00:11:13 +0000 Subject: [PATCH 362/443] equip_catalogue: input hsi event details by which to catalog equipment --- .../equipment/equipment_catalogue.py | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 1ba0f83cb4..89d0c804c7 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -6,13 +6,23 @@ from tlo.analysis.utils import extract_results -# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Declare whether to scale the counts to Malawi population size +# (True/False) do_scaling = True -# Declare output file names +# Declare as list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) +# (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') +catalog_by = ['treatment_id', 'facility_level'] +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +# %%% Output file names +# detailed CSV name output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' -output_file_name = 'equipment_annual_counts__by_Year_TreatmentID_FacLevel.csv' -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# requested details only CSV name +output_file_name_prefix = 'equipment_annual_counts__by_year_' +output_file_name_suffix = '.csv' +output_file_name_details_specified = '_'.join(catalog_by) +output_file_name = output_file_name_prefix + output_file_name_details_specified + output_file_name_suffix def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: @@ -78,7 +88,7 @@ def get_hsi_event_keys(_df): def create_equipment_catalogues(results_folder: Path, output_folder: Path): - # %% Catalogue equipment by all HSI event details + # %% Catalog equipment by all HSI event details sim_equipment = get_monthly_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) hsi_event_keys = get_hsi_event_keys_all_runs(results_folder) @@ -134,15 +144,15 @@ def details_col_to_str(details_col): print(f'{output_detailed_file_name} saved.') # --- - # %% Catalogue equipment by Treatment ID and Facility Levels + # %% Catalog equipment by requested details equipment_counts_by_date_treatment_id_level_df = final_df.copy() # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), # keeping only non-empty 'equipment' indexes + to_be_grouped_by = ['date'] + catalog_by + ['equipment'] equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( - ['date', 'treatment_id', 'facility_level', 'equipment'], + to_be_grouped_by, dropna=True - # TODO: make 'treatment_id', 'facility_level' to be an input ).sum() # Sum counts annually From 1435d221719db19f92474c03809f0e3fdec377ea Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 00:41:54 +0000 Subject: [PATCH 363/443] equip_catalogue: input time period by which to catalog equipment --- .../equipment/equipment_catalogue.py | 42 ++++++++++--------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 89d0c804c7..da62e30afd 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -10,19 +10,22 @@ # Declare whether to scale the counts to Malawi population size # (True/False) do_scaling = True -# Declare as list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) +# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) # (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') -catalog_by = ['treatment_id', 'facility_level'] +catalog_by_details = ['treatment_id', 'facility_level'] +# Declare which time period you want the equipment be grouped in the catalogue (choose only one) +# (periods: 'monthly', 'annual') +catalog_by_time = 'annual' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# TODO: verify inputs are as expected # %%% Output file names # detailed CSV name output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' # requested details only CSV name -output_file_name_prefix = 'equipment_annual_counts__by_year_' -output_file_name_suffix = '.csv' -output_file_name_details_specified = '_'.join(catalog_by) -output_file_name = output_file_name_prefix + output_file_name_details_specified + output_file_name_suffix +time_index = 'year' if catalog_by_time == 'annual' else 'date' +output_file_name = \ + 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: @@ -145,28 +148,29 @@ def details_col_to_str(details_col): # --- # %% Catalog equipment by requested details - equipment_counts_by_date_treatment_id_level_df = final_df.copy() + equipment_counts_by_time_and_requested_details = final_df.copy() # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), # keeping only non-empty 'equipment' indexes - to_be_grouped_by = ['date'] + catalog_by + ['equipment'] - equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( + to_be_grouped_by = ['date'] + catalog_by_details + ['equipment'] + equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( to_be_grouped_by, dropna=True ).sum() - # Sum counts annually - equipment_counts_by_date_treatment_id_level_df['year'] = \ - equipment_counts_by_date_treatment_id_level_df.index.get_level_values('date').year - # TODO: make annual/monthly results according to input - equipment_counts_by_date_treatment_id_level_df.set_index('year', append=True, inplace=True) - equipment_counts_by_date_treatment_id_level_df.index.droplevel('date') - equipment_counts_by_date_treatment_id_level_df = equipment_counts_by_date_treatment_id_level_df.groupby( - ['year', 'treatment_id', 'facility_level', 'equipment'] - ).sum() + if catalog_by_time == 'annual': + # Sum counts annually + equipment_counts_by_time_and_requested_details['year'] = \ + equipment_counts_by_time_and_requested_details.index.get_level_values('date').year + equipment_counts_by_time_and_requested_details.set_index('year', append=True, inplace=True) + equipment_counts_by_time_and_requested_details.index.droplevel('date') + to_be_grouped_by = ['year'] + catalog_by_details + ['equipment'] + equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( + to_be_grouped_by + ).sum() # Save the equipment counts CSV - equipment_counts_by_date_treatment_id_level_df.to_csv(output_folder / output_file_name) + equipment_counts_by_time_and_requested_details.to_csv(output_folder / output_file_name) print(f'{output_file_name} saved.') # # --- From 14a11a7c76858013c3361350809c762f83cbcbc3 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 00:48:08 +0000 Subject: [PATCH 364/443] equip_catalogue: list of requested details can be empty --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index da62e30afd..d69bc9e638 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -10,7 +10,7 @@ # Declare whether to scale the counts to Malawi population size # (True/False) do_scaling = True -# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose one or more) +# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose any number) # (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') catalog_by_details = ['treatment_id', 'facility_level'] # Declare which time period you want the equipment be grouped in the catalogue (choose only one) From 6d85fe13a65b3a5956c7b1d8237479a84520798c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 2 Dec 2023 01:44:39 +0000 Subject: [PATCH 365/443] equip_catalogue: verify inputs as expected & set output file names in 'create_equipment_catalogues' fnc --- .../equipment/equipment_catalogue.py | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index d69bc9e638..66fcff01ae 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -17,15 +17,6 @@ # (periods: 'monthly', 'annual') catalog_by_time = 'annual' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# TODO: verify inputs are as expected - -# %%% Output file names -# detailed CSV name -output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' -# requested details only CSV name -time_index = 'year' if catalog_by_time == 'annual' else 'date' -output_file_name = \ - 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: @@ -90,6 +81,27 @@ def get_hsi_event_keys(_df): def create_equipment_catalogues(results_folder: Path, output_folder: Path): + # %%% Verify inputs are as expected + assert isinstance(do_scaling, bool), "The input parameter 'do_scaling' must be a boolean (True or False)" + assert isinstance(catalog_by_details, list), "The input parameter 'catalog_by_details' must be a list" + event_details = \ + {'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint'} + for item in catalog_by_details: + assert isinstance(item, str) and item in event_details, \ + f"Each element in the input list 'catalog_by_details' must be a string and be one of the details:\n" \ + f"{event_details}" + assert catalog_by_time in {'monthly', 'annual'}, \ + "The input parameter 'catalog_by_time' must be one of the strings ('monthly' or 'annual')" + # --- + + # %%% Set output file names + # detailed CSV name + output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' + # requested details only CSV name + time_index = 'year' if catalog_by_time == 'annual' else 'date' + output_file_name = \ + 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' + # --- # %% Catalog equipment by all HSI event details sim_equipment = get_monthly_hsi_event_counts(results_folder) @@ -172,7 +184,7 @@ def details_col_to_str(details_col): # Save the equipment counts CSV equipment_counts_by_time_and_requested_details.to_csv(output_folder / output_file_name) print(f'{output_file_name} saved.') - # # --- + # --- return 0 From 934e52d9281f92d9e56f4e34be11b4670ccdec9c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 16:54:53 +0000 Subject: [PATCH 366/443] equip_catalogue: (1) detailed - equip set as string in one row, module_name before event_name, sorted by indexes; (2) added summary catal. (3) focused - only this cat. without empty equip rows and split by item per row --- .../equipment/equipment_catalogue.py | 62 +++++++++++++------ 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 66fcff01ae..6ba03916c8 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -99,8 +99,9 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' # requested details only CSV name time_index = 'year' if catalog_by_time == 'annual' else 'date' - output_file_name = \ + output_focused_file_name = \ 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' + output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id.csv' # --- # %% Catalog equipment by all HSI event details @@ -113,6 +114,12 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): def details_col_to_str(details_col): return details_col.apply(lambda x: ', '.join(map(str, x))) + def lists_of_strings_to_strings_of_list(list_of_strings_col): + return list_of_strings_col.apply(lambda x: "['" + "', '".join(map(str, x)) + "']") + + def strings_of_list_to_lists_of_strings(strings_of_list_col): + return strings_of_list_col.apply(lambda x: x.strip('][').split(', ')) + for col in hsi_event_keys.columns: df_col = sim_equipment_df[col].dropna() decoded_keys = df_col.index.get_level_values(1).astype(str).map(hsi_event_keys.at[0, col]) @@ -136,34 +143,38 @@ def details_col_to_str(details_col): # %%% df_col = pd.concat([df_col, pd.DataFrame(decoded_keys.tolist(), index=df_col.index)], axis=1) - # Make values in 'appt_footprint', 'beddays_footprint' columns to be string + # Make values in 'appt_footprint', 'beddays_footprint', and 'equipment' columns to be string df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) - # Explode the 'equipment' column - exploded_df = df_col.explode('equipment') - # Remove the 'event_details_key' and replace the index with hsi event details as indexes - exploded_df = exploded_df.droplevel(level=1) - exploded_df = exploded_df.set_index( - ['event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', - 'equipment'], append=True - ) - # Sum values with the same multi-index (keep also empty indexes) - exploded_df = exploded_df.groupby(level=exploded_df.index.names, dropna=False).sum() - # Add the results for the run 'col' to final_df - final_df = pd.concat([final_df, exploded_df], axis=1) + df_col['equipment'] = lists_of_strings_to_strings_of_list(df_col['equipment']) + df_col = (df_col.droplevel(level=1) + .set_index(['module_name', 'event_name', 'treatment_id', 'facility_level', 'appt_footprint', + 'beddays_footprint', 'equipment'], append=True)) + final_df = pd.concat([final_df, df_col], axis=1) # Replace NaN with 0 final_df.fillna(0, inplace=True) + final_df.sort_index(inplace=True) # Save the detailed equipment catalogue final_df.to_csv(output_folder / output_detailed_file_name) print(f'{output_detailed_file_name} saved.') # --- + # %% Catalog equipment summary + equipment_summary = final_df.copy() + equipment_summary = equipment_summary.groupby(['module_name', 'event_name', 'treatment_id', 'equipment']).sum() + equipment_summary = \ + equipment_summary.reset_index().set_index(['module_name', 'event_name', 'treatment_id', 'equipment']) + # Save the summary equipment catalogue + equipment_summary.index.to_frame().to_csv(output_folder / output_summary_file_name, index=False) + print(f'{output_summary_file_name} saved.') + # --- + # %% Catalog equipment by requested details equipment_counts_by_time_and_requested_details = final_df.copy() - # Sum counts for each equipment with the same date, treatment id, and facility level (remaining indexes removed), - # keeping only non-empty 'equipment' indexes + # Sum counts for each equipment set with the same date, treatment id, and facility level + # (remaining indexes removed), keeping only non-empty 'equipment' indexes to_be_grouped_by = ['date'] + catalog_by_details + ['equipment'] equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( to_be_grouped_by, @@ -181,9 +192,24 @@ def details_col_to_str(details_col): to_be_grouped_by ).sum() + # Remove rows with no equipment used + equipment_counts_by_time_and_requested_details.drop("['']", level='equipment', axis=0, inplace=True) + # Split the equipment by an item per row + equipment_counts_by_time_and_requested_details['equipment'] = \ + equipment_counts_by_time_and_requested_details.index.get_level_values('equipment') + equipment_counts_by_time_and_requested_details.index = \ + equipment_counts_by_time_and_requested_details.index.droplevel('equipment') + equipment_counts_by_time_and_requested_details['equipment'] = strings_of_list_to_lists_of_strings( + equipment_counts_by_time_and_requested_details['equipment'] + ) + exploded_df = equipment_counts_by_time_and_requested_details.explode('equipment') + exploded_df = exploded_df.set_index(['equipment'], append=True) + # Sum values with the same multi-index + exploded_df = exploded_df.groupby(level=exploded_df.index.names).sum() + # Save the equipment counts CSV - equipment_counts_by_time_and_requested_details.to_csv(output_folder / output_file_name) - print(f'{output_file_name} saved.') + exploded_df.to_csv(output_folder / output_focused_file_name) + print(f'{output_focused_file_name} saved.') # --- return 0 From bdbd8f73b859d2bac1e0f95c259ea0fb5d39a5e2 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 21:30:58 +0000 Subject: [PATCH 367/443] equip_catalogue: suffix (as input) added for output file names --- .../healthsystem/equipment/equipment_catalogue.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 6ba03916c8..4506892e8b 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -16,6 +16,8 @@ # Declare which time period you want the equipment be grouped in the catalogue (choose only one) # (periods: 'monthly', 'annual') catalog_by_time = 'annual' +# Suffix for output file names +suffix_file_names = '__5y_20Kpop_10runs' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -96,12 +98,13 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): # %%% Set output file names # detailed CSV name - output_detailed_file_name = 'equipment_monthly_counts__all_event_details.csv' + output_detailed_file_name = 'equipment_monthly_counts__all_event_details' + suffix_file_names + '.csv' # requested details only CSV name time_index = 'year' if catalog_by_time == 'annual' else 'date' output_focused_file_name = \ - 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + '.csv' - output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id.csv' + 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + \ + suffix_file_names + '.csv' + output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id)' + suffix_file_names + '.csv' # --- # %% Catalog equipment by all HSI event details From 070454c13881d50c2e41812e10dbafdfe487ebb4 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 10:26:11 +0000 Subject: [PATCH 368/443] hs: structure v2; alri+co: examples to test new structure --- src/tlo/methods/alri.py | 4 ++++ src/tlo/methods/contraception.py | 19 ++++++++++++++++++- src/tlo/methods/healthsystem.py | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index e4b0b46d17..c211e33179 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2290,6 +2290,8 @@ def __init__(self, module: Module, person_id: int, facility_level: str = "0", in self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels + self.set_essential_equipment({'Pulse oximeter'}) + # TODO: CORRECT --- an example with ess. equipm. set (which may or may not be used at the end) self.is_followup_following_treatment_failure = is_followup_following_treatment_failure if not inpatient: @@ -2596,6 +2598,8 @@ def _get_disease_classification_for_treatment_decision(self, 'cough_or_cold' (symptoms-based assessment) }.""" + self.update_equipment({'Pulse oximeter'}) + child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) imci_classification_based_on_symptoms = self._get_imci_classification_based_on_symptoms( diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 0a4884d0f3..2a615420ef 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1109,6 +1109,7 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level + self.set_essential_equipment({''}) @property def EXPECTED_APPT_FOOTPRINT(self): @@ -1144,6 +1145,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.update_equipment({ + 'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine' + }) + # Determine essential and optional items # TODO: we don't distinguish essential X optional for contraception methods yet, will need to update once we do items_essential = self.module.cons_codes[self.new_contraceptive] @@ -1170,7 +1176,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: @@ -1195,6 +1202,16 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive + # Update equipment if any needed for the method + if _new_contraceptive == 'female_sterilization': + self.update_equipment({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' + }) + elif _new_contraceptive == 'IUD': + self.update_equipment({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' + }) + else: _new_contraceptive = "not_using" diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index af0610fe6c..6021f0eb46 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -183,6 +183,7 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + # self.set_essential_equipment({''}) # HSI needs this attribute, but it is not defined in the Base class self.EQUIPMENT = set() @property @@ -339,6 +340,37 @@ def make_appt_footprint(self, dict_of_appts): "values" ) + def set_essential_equipment(self, set_of_equip): + """Helper function to set essential equipment. + + Should be passed a set of equipment items names (strings) or an empty set. + """ + # Set EQUIPMENT if the given set_of_equip in correct format, ie a set of strings or an empty set + if isinstance(set_of_equip, set) and all(isinstance(item, str) for item in set_of_equip): + self.ESSENTIAL_EQUIPMENT = set_of_equip + return 0 + + raise ValueError( + "Argument to set_essential_equipment should be an empty set or a set of strings of equipment item names " + "from ResourceFile_Equipment.csv." + ) + + def update_equipment(self, set_of_equip): + """Helper function to update equipment. + + Should be passed a set of equipment item names (strings). + """ + # Update EQUIPMENT if the given set_of_equip in correct format, ie a non-empty set of strings + if isinstance(set_of_equip, set) and (all(isinstance(item, str) for item in set_of_equip)) and \ + (set_of_equip not in [set(), None, {''}]): + self.EQUIPMENT.update(set_of_equip) + return self.EQUIPMENT.discard('') + + raise ValueError( + "Argument to update_equipment should be a non-empty set of strings of equipment item names " + "from ResourceFile_Equipment.csv." + ) + def initialise(self): """Initialise the HSI: * Set the facility_info From 99be34a2d17b217baaceab81df6e637c612604fa Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 10:27:05 +0000 Subject: [PATCH 369/443] equip_catalogue: TODO added --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 4506892e8b..877aa7460b 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -21,6 +21,7 @@ # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# TODO: Could I have use the bin_hsi_event_details from src/tlo/analysis/utils.py instead? If so, how? def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: """Returned pd.DataFrame gives the monthly counts of all the hsi event details logged (details as keys) for each simulated month. From 5076e3ba38f62ba3259245b7bf6fc96c79781145 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Dec 2023 22:39:54 +0000 Subject: [PATCH 370/443] equip_catalogue: typo --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 877aa7460b..dd2dde8015 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -105,7 +105,7 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): output_focused_file_name = \ 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + \ suffix_file_names + '.csv' - output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id)' + suffix_file_names + '.csv' + output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id' + suffix_file_names + '.csv' # --- # %% Catalog equipment by all HSI event details From d47b53ef2d8b36198788786e281d9680f45f483f Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 11:08:04 +0000 Subject: [PATCH 371/443] equip_catalogue: TODO added --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index dd2dde8015..4b91510c61 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -6,6 +6,7 @@ from tlo.analysis.utils import extract_results +# TODO: make these to be arguments of called fnc # %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% # Declare whether to scale the counts to Malawi population size # (True/False) From 057fb440bbbfcc942d4d9be6946e8c6fbc93a0be Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 11:37:07 +0000 Subject: [PATCH 372/443] equip_catalogue: bug fixed --- src/scripts/healthsystem/equipment/equipment_catalogue.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 4b91510c61..4f47af6d0d 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -18,7 +18,7 @@ # (periods: 'monthly', 'annual') catalog_by_time = 'annual' # Suffix for output file names -suffix_file_names = '__5y_20Kpop_10runs' +suffix_file_names = '__2y_2Kpop_4runs_1draw' # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% @@ -123,7 +123,8 @@ def lists_of_strings_to_strings_of_list(list_of_strings_col): return list_of_strings_col.apply(lambda x: "['" + "', '".join(map(str, x)) + "']") def strings_of_list_to_lists_of_strings(strings_of_list_col): - return strings_of_list_col.apply(lambda x: x.strip('][').split(', ')) + lists_of_strings_col = strings_of_list_col.apply(lambda x: x.strip('][').split("'")) + return lists_of_strings_col.apply(lambda x: [s for s in x if (s != '' and s != ', ')]) for col in hsi_event_keys.columns: df_col = sim_equipment_df[col].dropna() From 2bf3e8ae13cbfb9733c7d72caff3ca9ff68283f1 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 18:57:30 +0000 Subject: [PATCH 373/443] brc: TODO added --- src/tlo/methods/breast_cancer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1ce9ad2bf6..2529911d84 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -646,6 +646,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. + # TODO: but the appt footprints suggests mammography to be provided def apply(self, person_id, squeeze_factor): df = self.sim.population.props From fa3380f52c17bc39b55b655c69203bb605db2345 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 19:00:02 +0000 Subject: [PATCH 374/443] hs: rm prints --- src/tlo/methods/healthsystem.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 6021f0eb46..9fd7428f76 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1876,8 +1876,6 @@ def write_to_hsi_log( description="record of each HSI event" ) if did_run: - print("\nevent_details") - print(event_details) if self._hsi_event_count_log_period is not None: event_details_key = self._hsi_event_details.setdefault( event_details, len(self._hsi_event_details) From 4b7904a443bd6fb6b065ec44c1d8d38dd1328d13 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 19:01:28 +0000 Subject: [PATCH 375/443] hs: rm old code --- src/tlo/methods/healthsystem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 9fd7428f76..8bd9fac9ea 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1887,7 +1887,6 @@ def write_to_hsi_log( squeeze_factor=squeeze_factor, appt_footprint=event_details.appt_footprint, level=event_details.facility_level, - equipment=equipment, ) def call_and_record_never_ran_hsi_event(self, hsi_event, priority=None): From 8c8bcc544eeac5a03f877c539d1d2bdd1b20e727 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sat, 13 Jan 2024 19:04:14 +0000 Subject: [PATCH 376/443] hs: rm/add accidentally added/rmd commas --- src/tlo/methods/healthsystem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 8bd9fac9ea..1315b93c0d 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1919,7 +1919,7 @@ def write_to_never_ran_hsi_log( event_details: HSIEventDetails, person_id: int, facility_id: Optional[int], - priority: int + priority: int, ): """Write the log `HSI_Event` and add to the summary counter.""" logger.debug( @@ -1944,7 +1944,7 @@ def write_to_never_ran_hsi_log( treatment_id=event_details.treatment_id, hsi_event_name=event_details.event_name, appt_footprint=event_details.appt_footprint, - level=event_details.facility_level + level=event_details.facility_level, ) def log_current_capabilities_and_usage(self): @@ -2204,7 +2204,7 @@ def run_individual_level_events_in_mode_0_or_1(self, actual_appt_footprint=actual_appt_footprint, squeeze_factor=squeeze_factor, did_run=True, - priority=_priority, + priority=_priority ) # if not ok_to_run From 6b03eddf9d2a037d96d61919125bf5d306747cfe Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 15 Jan 2024 16:36:57 +0000 Subject: [PATCH 377/443] hs & alri+co: rename and correct return of fncs related to equipment --- src/tlo/methods/alri.py | 4 ++-- src/tlo/methods/contraception.py | 8 +++---- src/tlo/methods/healthsystem.py | 38 +++++++++++++++++--------------- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index c211e33179..640d4ec054 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2290,7 +2290,7 @@ def __init__(self, module: Module, person_id: int, facility_level: str = "0", in self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels - self.set_essential_equipment({'Pulse oximeter'}) + self.set_equipment_essential_to_run_event({'Pulse oximeter'}) # TODO: CORRECT --- an example with ess. equipm. set (which may or may not be used at the end) self.is_followup_following_treatment_failure = is_followup_following_treatment_failure @@ -2598,7 +2598,7 @@ def _get_disease_classification_for_treatment_decision(self, 'cough_or_cold' (symptoms-based assessment) }.""" - self.update_equipment({'Pulse oximeter'}) + self.add_equipment({'Pulse oximeter'}) child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 2a615420ef..dab1151ed4 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1109,7 +1109,7 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level - self.set_essential_equipment({''}) + self.set_equipment_essential_to_run_event({''}) @property def EXPECTED_APPT_FOOTPRINT(self): @@ -1146,7 +1146,7 @@ def apply(self, person_id, squeeze_factor): 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.update_equipment({ + self.add_equipment({ 'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine' }) @@ -1204,11 +1204,11 @@ def apply(self, person_id, squeeze_factor): # Update equipment if any needed for the method if _new_contraceptive == 'female_sterilization': - self.update_equipment({ + self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' }) elif _new_contraceptive == 'IUD': - self.update_equipment({ + self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' }) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1315b93c0d..6a735baddf 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -183,7 +183,10 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - # self.set_essential_equipment({''}) # HSI needs this attribute, but it is not defined in the Base class + # self.set_equipment_essential_to_run_event({''}) # HSI needs this attribute, but it is not defined in the Base + # class to allow verification of its existence as a test for + # each HSI event, showing that equipment setup was thought + # through for the event. self.EQUIPMENT = set() @property @@ -340,36 +343,35 @@ def make_appt_footprint(self, dict_of_appts): "values" ) - def set_essential_equipment(self, set_of_equip): + def set_equipment_essential_to_run_event(self, set_of_equip): """Helper function to set essential equipment. Should be passed a set of equipment items names (strings) or an empty set. """ # Set EQUIPMENT if the given set_of_equip in correct format, ie a set of strings or an empty set - if isinstance(set_of_equip, set) and all(isinstance(item, str) for item in set_of_equip): - self.ESSENTIAL_EQUIPMENT = set_of_equip - return 0 + if not isinstance(set_of_equip, set) or any(not isinstance(item, str) for item in set_of_equip): + raise ValueError( + "Argument to set_equipment_essential_to_run_event should be an empty set or a set of strings of " + "equipment item names from ResourceFile_Equipment.csv." + ) - raise ValueError( - "Argument to set_essential_equipment should be an empty set or a set of strings of equipment item names " - "from ResourceFile_Equipment.csv." - ) + self.ESSENTIAL_EQUIPMENT = set_of_equip - def update_equipment(self, set_of_equip): + def add_equipment(self, set_of_equip): """Helper function to update equipment. Should be passed a set of equipment item names (strings). """ # Update EQUIPMENT if the given set_of_equip in correct format, ie a non-empty set of strings - if isinstance(set_of_equip, set) and (all(isinstance(item, str) for item in set_of_equip)) and \ - (set_of_equip not in [set(), None, {''}]): - self.EQUIPMENT.update(set_of_equip) - return self.EQUIPMENT.discard('') + if not isinstance(set_of_equip, set) or any(not isinstance(item, str) for item in set_of_equip) or \ + (set_of_equip in [set(), None, {''}]): + raise ValueError( + "Argument to add_equipment should be a non-empty set of strings of " + "equipment item names from ResourceFile_Equipment.csv." + ) - raise ValueError( - "Argument to update_equipment should be a non-empty set of strings of equipment item names " - "from ResourceFile_Equipment.csv." - ) + self.EQUIPMENT.update(set_of_equip) + self.EQUIPMENT.discard('') def initialise(self): """Initialise the HSI: From c37e1de9ac120eb7f2f37c0fdcc05722862a4c19 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 16 Jan 2024 17:44:06 +0000 Subject: [PATCH 378/443] hs: log equip item codes instead of names --- src/tlo/methods/healthsystem.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 6a735baddf..0411bdbe9a 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -343,6 +343,10 @@ def make_appt_footprint(self, dict_of_appts): "values" ) + def get_equip_item_code_from_item_name(self, lookup_df: pd.DataFrame, equip_item_name: str) -> int: + """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" + return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) + def set_equipment_essential_to_run_event(self, set_of_equip): """Helper function to set essential equipment. @@ -369,8 +373,13 @@ def add_equipment(self, set_of_equip): "Argument to add_equipment should be a non-empty set of strings of " "equipment item names from ResourceFile_Equipment.csv." ) - - self.EQUIPMENT.update(set_of_equip) + # from the set of equip item names create a set of item codes + # this function is calling parameters from this + equip_codes = set(self.get_equip_item_code_from_item_name( + self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'], item_name + ) for item_name in set_of_equip + ) + self.EQUIPMENT.update(equip_codes) self.EQUIPMENT.discard('') def initialise(self): @@ -561,6 +570,9 @@ class HealthSystem(Module): "Availability of beds. If 'default' then use the availability specified in the ResourceFile; if " "'none', then let no beds be ever be available; if 'all', then all beds are always available. NB. This " "parameter is over-ridden if an argument is provided to the module initialiser."), + 'equip_item_and_package_code_lookups': Parameter( + Types.DATA_FRAME, "Items based on the the HSSP III 1K Equipment Costing (SEL Costing Sheet): " + "https://www.health.gov.mw/download/hssp-iii/, packages created in consultation with clinicians."), # Service Availability 'Service_Availability': Parameter( @@ -848,6 +860,10 @@ def read_parameters(self, data_folder): self.parameters['BedCapacity'] = pd.read_csv( path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Bed_Capacity.csv') + # Read in ResourceFile_Equipment + self.parameters['equip_item_and_package_code_lookups'] = pd.read_csv( + path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Equipment.csv') + # Data on the priority of each Treatment_ID that should be adopted in the queueing system according to different # priority policies. Load all policies at this stage, and decide later which one to adopt. self.parameters['priority_rank'] = pd.read_excel(path_to_resourcefiles_for_healthsystem / 'priority_policies' / From 99ad1389eabbf4c9c3d92346059997479cf870e6 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 17 Jan 2024 17:41:47 +0000 Subject: [PATCH 379/443] equip_catalogue: updated for logged equip item codes --- .../equipment/equipment_catalogue.py | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index 4f47af6d0d..aa4552e536 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -109,6 +109,12 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id' + suffix_file_names + '.csv' # --- + # %%% Load RF + # Equipment + equip_resource_items_pkgs_df = pd.read_csv( + 'resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv' + ) + # %% Catalog equipment by all HSI event details sim_equipment = get_monthly_hsi_event_counts(results_folder) sim_equipment_df = pd.DataFrame(sim_equipment) @@ -119,8 +125,15 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): def details_col_to_str(details_col): return details_col.apply(lambda x: ', '.join(map(str, x))) - def lists_of_strings_to_strings_of_list(list_of_strings_col): - return list_of_strings_col.apply(lambda x: "['" + "', '".join(map(str, x)) + "']") + def get_equip_item_name_from_item_code(lookup_df: pd.DataFrame, equip_item_code: str) -> int: + """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" + return str(pd.unique(lookup_df.loc[lookup_df["Equip_Code"] == equip_item_code, "Equip_Item"])[0]) + + def lists_of_equip_item_codes_to_strings_of_list_of_equip_item_names(list_of_equip_item_codes_col): + return list_of_equip_item_codes_col.apply( + lambda x: + str(sorted([get_equip_item_name_from_item_code(equip_resource_items_pkgs_df, item_code) for item_code in x])) + ) def strings_of_list_to_lists_of_strings(strings_of_list_col): lists_of_strings_col = strings_of_list_col.apply(lambda x: x.strip('][').split("'")) @@ -152,7 +165,7 @@ def strings_of_list_to_lists_of_strings(strings_of_list_col): # Make values in 'appt_footprint', 'beddays_footprint', and 'equipment' columns to be string df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) - df_col['equipment'] = lists_of_strings_to_strings_of_list(df_col['equipment']) + df_col['equipment'] = lists_of_equip_item_codes_to_strings_of_list_of_equip_item_names(df_col['equipment']) df_col = (df_col.droplevel(level=1) .set_index(['module_name', 'event_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint', 'equipment'], append=True)) @@ -199,8 +212,7 @@ def strings_of_list_to_lists_of_strings(strings_of_list_col): ).sum() # Remove rows with no equipment used - equipment_counts_by_time_and_requested_details.drop("['']", level='equipment', axis=0, inplace=True) - # Split the equipment by an item per row + equipment_counts_by_time_and_requested_details.drop("[]", level='equipment', axis=0, inplace=True) equipment_counts_by_time_and_requested_details['equipment'] = \ equipment_counts_by_time_and_requested_details.index.get_level_values('equipment') equipment_counts_by_time_and_requested_details.index = \ @@ -230,4 +242,5 @@ def strings_of_list_to_lists_of_strings(strings_of_list_col): results_folder=args.results_folder, output_folder=args.results_folder, ) -# NB. Edit run configuration, the Parameters: "./outputs/sejjej5@ucl.ac.uk/long_run_all_diseases-2023-09-04T233551Z" +# NB. Edit run configuration, the Parameters: +# "./outputs/sejjej5@ucl.ac.uk/equip_jobs/long_run_all_diseases-2023-09-04T233551Z" From a281e56c204bd95588c2a7c8e60bcb30f15e3be2 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 19 Jan 2024 13:58:10 +0000 Subject: [PATCH 380/443] brc: change Andrew suggested --- src/tlo/methods/breast_cancer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 2529911d84..47f583ff64 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -663,7 +663,7 @@ def apply(self, person_id, squeeze_factor): if not pd.isnull(df.at[person_id, "brc_date_diagnosis"]): return hs.get_blank_appt_footprint() - df.brc_breast_lump_discernible_investigated = True + df.at[person_id, 'brc_breast_lump_discernible_investigated'] = True # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this From 1787ffe5bf8549a5e3f7dc82469124873bb219e2 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 19 Jan 2024 14:53:41 +0000 Subject: [PATCH 381/443] hs: updates for better readability; rm unused code --- src/tlo/methods/healthsystem.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 0411bdbe9a..562d268e6f 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -6,7 +6,7 @@ from collections.abc import Iterable from itertools import repeat from pathlib import Path -from typing import Dict, List, NamedTuple, Optional, Tuple, Union +from typing import Dict, List, NamedTuple, Optional, Tuple, Union, Set import numpy as np import pandas as pd @@ -347,7 +347,7 @@ def get_equip_item_code_from_item_name(self, lookup_df: pd.DataFrame, equip_item """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) - def set_equipment_essential_to_run_event(self, set_of_equip): + def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: """Helper function to set essential equipment. Should be passed a set of equipment items names (strings) or an empty set. @@ -361,7 +361,7 @@ def set_equipment_essential_to_run_event(self, set_of_equip): self.ESSENTIAL_EQUIPMENT = set_of_equip - def add_equipment(self, set_of_equip): + def add_equipment(self, set_of_equip: Set[str]) -> None: """Helper function to update equipment. Should be passed a set of equipment item names (strings). @@ -375,12 +375,13 @@ def add_equipment(self, set_of_equip): ) # from the set of equip item names create a set of item codes # this function is calling parameters from this - equip_codes = set(self.get_equip_item_code_from_item_name( - self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'], item_name - ) for item_name in set_of_equip + equip_codes = set( + self.get_equip_item_code_from_item_name( + self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'], + item_name + ) for item_name in set_of_equip ) self.EQUIPMENT.update(equip_codes) - self.EQUIPMENT.discard('') def initialise(self): """Initialise the HSI: From f9616405b542775aa27b5d0dd8b54f368a71bac1 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 19 Jan 2024 15:59:53 +0000 Subject: [PATCH 382/443] hs: PEP8 --- src/tlo/methods/healthsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 562d268e6f..0d4807eab0 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -6,7 +6,7 @@ from collections.abc import Iterable from itertools import repeat from pathlib import Path -from typing import Dict, List, NamedTuple, Optional, Tuple, Union, Set +from typing import Dict, List, NamedTuple, Optional, Set, Tuple, Union import numpy as np import pandas as pd From 12142b0d887ee53b7c78bb09fa59caf46776fbd8 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 19 Jan 2024 22:45:00 +0000 Subject: [PATCH 383/443] hs: get_equip_item_code_from_item_name fnc updated; ESS.EQUIP as codes --- src/tlo/methods/healthsystem.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 0d4807eab0..946bb32643 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -343,8 +343,9 @@ def make_appt_footprint(self, dict_of_appts): "values" ) - def get_equip_item_code_from_item_name(self, lookup_df: pd.DataFrame, equip_item_name: str) -> int: + def get_equip_item_code_from_item_name(self, equip_item_name: str) -> int: """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" + lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'] return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: @@ -359,7 +360,11 @@ def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: "equipment item names from ResourceFile_Equipment.csv." ) - self.ESSENTIAL_EQUIPMENT = set_of_equip + if set_of_equip not in [set(), None, {''}]: + equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) + self.ESSENTIAL_EQUIPMENT = equip_codes + else: + self.ESSENTIAL_EQUIPMENT = set() def add_equipment(self, set_of_equip: Set[str]) -> None: """Helper function to update equipment. @@ -375,12 +380,7 @@ def add_equipment(self, set_of_equip: Set[str]) -> None: ) # from the set of equip item names create a set of item codes # this function is calling parameters from this - equip_codes = set( - self.get_equip_item_code_from_item_name( - self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'], - item_name - ) for item_name in set_of_equip - ) + equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) self.EQUIPMENT.update(equip_codes) def initialise(self): From 36f0523f0e07226a7d46f69b496560ae675c7fd0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 21 Jan 2024 21:58:17 +0000 Subject: [PATCH 384/443] equip_catalogue: add item codes to catalogue by requested details (1 item per row) --- .../equipment/equipment_catalogue.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py index aa4552e536..4dcd533903 100644 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ b/src/scripts/healthsystem/equipment/equipment_catalogue.py @@ -125,14 +125,20 @@ def create_equipment_catalogues(results_folder: Path, output_folder: Path): def details_col_to_str(details_col): return details_col.apply(lambda x: ', '.join(map(str, x))) - def get_equip_item_name_from_item_code(lookup_df: pd.DataFrame, equip_item_code: str) -> int: - """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" + def get_equip_item_name_from_item_code(equip_item_code: int) -> str: + """Helper function to provide the equip item name (a string) when provided with the equip_item_code (an int).""" + lookup_df = equip_resource_items_pkgs_df return str(pd.unique(lookup_df.loc[lookup_df["Equip_Code"] == equip_item_code, "Equip_Item"])[0]) + def get_equip_item_code_from_item_name(equip_item_name: str) -> int: + """Helper function to provide the equip item code (an int) when provided with the equip_item_name (a string)""" + lookup_df = equip_resource_items_pkgs_df + return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) + def lists_of_equip_item_codes_to_strings_of_list_of_equip_item_names(list_of_equip_item_codes_col): return list_of_equip_item_codes_col.apply( lambda x: - str(sorted([get_equip_item_name_from_item_code(equip_resource_items_pkgs_df, item_code) for item_code in x])) + str(sorted([get_equip_item_name_from_item_code(item_code) for item_code in x])) ) def strings_of_list_to_lists_of_strings(strings_of_list_col): @@ -221,7 +227,10 @@ def strings_of_list_to_lists_of_strings(strings_of_list_col): equipment_counts_by_time_and_requested_details['equipment'] ) exploded_df = equipment_counts_by_time_and_requested_details.explode('equipment') - exploded_df = exploded_df.set_index(['equipment'], append=True) + # Add column with equip item code + exploded_df['equip_code'] = exploded_df['equipment'].apply(lambda x: get_equip_item_code_from_item_name(x)) + exploded_df = exploded_df.set_index(['equipment', 'equip_code'], append=True) + # Sum values with the same multi-index exploded_df = exploded_df.groupby(level=exploded_df.index.names).sum() From b40f9558567029fbf40ff278922168cd1deef35c Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 21 Jan 2024 18:47:29 +0000 Subject: [PATCH 385/443] hs: allow adding equip by pkg name(s) --- src/tlo/methods/healthsystem.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 946bb32643..1c27fcee75 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -348,6 +348,12 @@ def get_equip_item_code_from_item_name(self, equip_item_name: str) -> int: lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'] return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) + def get_equip_item_codes_from_pkg_name(self, equip_pkg_name: str) -> Set[int]: + """Helper function to provide the equip_item_codes (a set of ints) when provided with the equip_pkg_name of the + equipment package""" + lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'] + return set(lookup_df.loc[lookup_df["Equip_Pkg"] == equip_pkg_name, "Equip_Code"]) + def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: """Helper function to set essential equipment. @@ -378,11 +384,26 @@ def add_equipment(self, set_of_equip: Set[str]) -> None: "Argument to add_equipment should be a non-empty set of strings of " "equipment item names from ResourceFile_Equipment.csv." ) - # from the set of equip item names create a set of item codes - # this function is calling parameters from this + # from the set of equip item names create a set of equip item codes equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) self.EQUIPMENT.update(equip_codes) + def add_equipment_from_pkg(self, set_of_pkgs: Set[str]) -> None: + """Helper function to update equipment with equipment from pkg(s). + + Should be passed a set of equipment pkgs names (strings). + """ + # Update EQUIPMENT if the given set_of_pkgs in correct format, ie a non-empty set of strings + if not isinstance(set_of_pkgs, set) or any(not isinstance(item, str) for item in set_of_pkgs) or \ + (set_of_pkgs in [set(), None, {''}]): + raise ValueError( + "Argument to add_equipment_from_pkg should be a non-empty set of strings of " + "equipment pkg names from ResourceFile_Equipment.csv." + ) + # update EQUIPMENT with eqip item codes from equip pkgs with provided names + for pkg_name in set_of_pkgs: + self.EQUIPMENT.update(self.get_equip_item_codes_from_pkg_name(pkg_name)) + def initialise(self): """Initialise the HSI: * Set the facility_info From 9c8b6ad3655e326095ca469cf3dd178f632fcfff Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Sun, 21 Jan 2024 19:04:31 +0000 Subject: [PATCH 386/443] co & RF_Equip: an example of usage of equipment pkg --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- src/tlo/methods/contraception.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index f0a2b53c9f..90012472cb 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cd2c3558a4d30dfb6d913054513e1d6df1b91f4955b60371e762eff05ac99afd -size 32759 +oid sha256:8b5c09ef0800c69ed916edab8bf469ce83c47a09e1e75c7263a44c24f692ed3f +size 33197 diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index dab1151ed4..b672407c3b 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1207,6 +1207,11 @@ def apply(self, person_id, squeeze_factor): self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' }) + self.add_equipment_from_pkg({ + 'Minor Surgery' + }) + # TODO: this is just an example - update once figured out what we want in the pkgs + # (! Update also the RF_Equipment accordingly !) elif _new_contraceptive == 'IUD': self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' From 0552e48cb9310692fd95c9e0d884f3e70a7b9944 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 24 Jan 2024 15:32:31 +0000 Subject: [PATCH 387/443] utils: use pandas fnc (instead of make one) --- src/tlo/analysis/utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index a741eefee3..53b755a7cf 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -18,6 +18,7 @@ import numpy as np import pandas as pd import squarify +from pandas.api.types import is_numeric_dtype from tlo import Date, Simulation, logging, util from tlo.logging.reader import LogData @@ -282,9 +283,6 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series: # get number of draws and numbers of runs info = get_scenario_info(results_folder) - def is_number(element): - return isinstance(element, (int, float)) - # Collect results from each draw/run res = dict() for draw in range(info['number_of_draws']): @@ -296,7 +294,7 @@ def is_number(element): df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key] output_from_eval: pd.Series = generate_series(df) assert pd.Series == type(output_from_eval), 'Custom command does not generate a pd.Series' - if output_from_eval.apply(is_number).all(): + if is_numeric_dtype(output_from_eval): res[draw_run] = output_from_eval * get_multiplier(draw, run) else: res[draw_run] = output_from_eval From 0106e20b6cb6661ee4d41120d40b123171067000 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 24 Jan 2024 22:47:41 +0000 Subject: [PATCH 388/443] hs: ESS_EQUIP as HSI_Event's attribute; if settings of ESS_EQUIP forgotten -> set to empty & warn --- src/tlo/methods/healthsystem.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1c27fcee75..68a901017b 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -183,12 +183,14 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + self.ESSENTIAL_EQUIPMENT = None # self.set_equipment_essential_to_run_event({''}) # HSI needs this attribute, but it is not defined in the Base # class to allow verification of its existence as a test for # each HSI event, showing that equipment setup was thought # through for the event. self.EQUIPMENT = set() - + self._hsi_event_names_missing_ess_equip = set() # The names of hsi events for which the settings of + # essential equipment is missing. @property def bed_days_allocated_to_this_event(self): if self._received_info_about_bed_days is None: @@ -435,6 +437,11 @@ def initialise(self): # Do checks _ = self._check_if_appt_footprint_can_run() + # Set essential equip to empty set if not exists and warn about missing settings + if self.ESSENTIAL_EQUIPMENT is None: + self.set_equipment_essential_to_run_event({''}) + self._hsi_event_names_missing_ess_equip.update(self.__class__.__name__) + def _check_if_appt_footprint_can_run(self): """Check that event (if individual level) is able to run with this configuration of officers (i.e. check that this does not demand officers that are _never_ available), and issue warning if not.""" @@ -469,6 +476,18 @@ def as_namedtuple( equipment=(tuple(sorted(self.EQUIPMENT))) ) + def on_simulation_end(self): + """Do tasks at the end of the simulation: Raise warning and enter to log the set of hsi event names which were + initialised but the settings of essential equipment is missing.""" + if self._hsi_event_names_missing_ess_equip: + warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential" + f"equipment is missing:/n" + f"{self._hsi_event_names_missing_ess_equip}")) + logger.info( + key="hsi_event_names_missing_ess_equip", + data={"event_names": self._hsi_event_names_missing_ess_equip} + ) + class HSIEventWrapper(Event): """This is wrapper that contains an HSI event. From 455f4e0943acc50078614fc7dccaa699de2263ae Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 24 Jan 2024 23:58:52 +0000 Subject: [PATCH 389/443] hs: fixed saving _hsi_event_names_missing_ess_equip --- src/tlo/methods/healthsystem.py | 34 ++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 68a901017b..da17f0b89e 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -189,8 +189,7 @@ def __init__(self, module, *args, **kwargs): # each HSI event, showing that equipment setup was thought # through for the event. self.EQUIPMENT = set() - self._hsi_event_names_missing_ess_equip = set() # The names of hsi events for which the settings of - # essential equipment is missing. + @property def bed_days_allocated_to_this_event(self): if self._received_info_about_bed_days is None: @@ -440,7 +439,7 @@ def initialise(self): # Set essential equip to empty set if not exists and warn about missing settings if self.ESSENTIAL_EQUIPMENT is None: self.set_equipment_essential_to_run_event({''}) - self._hsi_event_names_missing_ess_equip.update(self.__class__.__name__) + self.sim.modules['HealthSystem']._hsi_event_names_missing_ess_equip.update(self.__class__.__name__) def _check_if_appt_footprint_can_run(self): """Check that event (if individual level) is able to run with this configuration of officers (i.e. check that @@ -476,19 +475,6 @@ def as_namedtuple( equipment=(tuple(sorted(self.EQUIPMENT))) ) - def on_simulation_end(self): - """Do tasks at the end of the simulation: Raise warning and enter to log the set of hsi event names which were - initialised but the settings of essential equipment is missing.""" - if self._hsi_event_names_missing_ess_equip: - warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential" - f"equipment is missing:/n" - f"{self._hsi_event_names_missing_ess_equip}")) - logger.info( - key="hsi_event_names_missing_ess_equip", - data={"event_names": self._hsi_event_names_missing_ess_equip} - ) - - class HSIEventWrapper(Event): """This is wrapper that contains an HSI event. @@ -858,6 +844,9 @@ def __init__( "'year', 'simulation' or None." ) + self._hsi_event_names_missing_ess_equip = set() # The names of HSI events for which the settings of essential + # equipment is missing. + def read_parameters(self, data_folder): path_to_resourcefiles_for_healthsystem = Path(self.resourcefilepath) / 'healthsystem' @@ -1017,7 +1006,9 @@ def on_birth(self, mother_id, child_id): self.bed_days.on_birth(self.sim.population.props, mother_id, child_id) def on_simulation_end(self): - """Put out to the log the information from the tracker of the last day of the simulation""" + """Put out to the log the information from the tracker of the last day of the simulation. + Raise warning and enter to log the set of hsi event names which were initialised but the settings of essential + equipment is missing.""" self.bed_days.on_simulation_end() self.consumables.on_simulation_end() if self._hsi_event_count_log_period == "simulation": @@ -1043,6 +1034,15 @@ def on_simulation_end(self): } ) + if self._hsi_event_names_missing_ess_equip: + warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential" + f"equipment is missing:/n" + f"{self._hsi_event_names_missing_ess_equip}")) + logger_summary.info( + key="hsi_event_names_missing_ess_equip", + data={"event_names": self._hsi_event_names_missing_ess_equip} + ) + def setup_priority_policy(self): # Determine name of policy to be considered **at the start of the simulation**. From 503036dc65a5d3f9d2c8fcebe51f0c07acb4a7f7 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 29 Jan 2024 17:36:24 +0000 Subject: [PATCH 390/443] hs: fixed updating _hsi_event_names_missing_ess_equip --- src/tlo/methods/healthsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index da17f0b89e..63570701bd 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -439,7 +439,7 @@ def initialise(self): # Set essential equip to empty set if not exists and warn about missing settings if self.ESSENTIAL_EQUIPMENT is None: self.set_equipment_essential_to_run_event({''}) - self.sim.modules['HealthSystem']._hsi_event_names_missing_ess_equip.update(self.__class__.__name__) + self.sim.modules['HealthSystem']._hsi_event_names_missing_ess_equip.update({self.__class__.__name__}) def _check_if_appt_footprint_can_run(self): """Check that event (if individual level) is able to run with this configuration of officers (i.e. check that @@ -1035,7 +1035,7 @@ def on_simulation_end(self): ) if self._hsi_event_names_missing_ess_equip: - warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential" + warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential " f"equipment is missing:/n" f"{self._hsi_event_names_missing_ess_equip}")) logger_summary.info( From 8ea0b27e2f65e4c7bcc4aefa7d84b379900eda81 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 29 Jan 2024 18:36:20 +0000 Subject: [PATCH 391/443] hs: TODO smt odd going on with hsi_event_names_missing_ess_equip warning --- src/tlo/methods/healthsystem.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 63570701bd..1ca98dfe70 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1042,6 +1042,10 @@ def on_simulation_end(self): key="hsi_event_names_missing_ess_equip", data={"event_names": self._hsi_event_names_missing_ess_equip} ) + # TODO: smt odd is going on, some hsi events were logged, according to my equipment_catalogue script, + # for which the essential equipment is not define, but they are not included in this warning. + # E.g. HSI_BladderCancer_Investigation_Following_Blood_Urine, HSI_BladderCancer_StartTreatment, + # HSI_BreastCancer_Investigation_Following_breast_lump_discernible, ... def setup_priority_policy(self): From 7ad13cc5391f3bc703b078c48e374471a6ba04b5 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 29 Jan 2024 18:37:10 +0000 Subject: [PATCH 392/443] hs: sort hsi_event_names_missing_ess_equip warning --- src/tlo/methods/healthsystem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1ca98dfe70..4bb0fc3a26 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1035,12 +1035,13 @@ def on_simulation_end(self): ) if self._hsi_event_names_missing_ess_equip: + hsi_event_names_missing_ess_equip = sorted(self._hsi_event_names_missing_ess_equip) warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential " f"equipment is missing:/n" - f"{self._hsi_event_names_missing_ess_equip}")) + f"{hsi_event_names_missing_ess_equip}")) logger_summary.info( key="hsi_event_names_missing_ess_equip", - data={"event_names": self._hsi_event_names_missing_ess_equip} + data={"event_names": hsi_event_names_missing_ess_equip} ) # TODO: smt odd is going on, some hsi events were logged, according to my equipment_catalogue script, # for which the essential equipment is not define, but they are not included in this warning. From 39a5d21faeda87837d6c5e8211af365d8b7e2356 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 29 Jan 2024 18:38:06 +0000 Subject: [PATCH 393/443] hs: equip_item_and_package_code_lookups renamed to equip_item_and_package_lookups --- src/tlo/methods/healthsystem.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 4bb0fc3a26..09521ce712 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -346,13 +346,13 @@ def make_appt_footprint(self, dict_of_appts): def get_equip_item_code_from_item_name(self, equip_item_name: str) -> int: """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" - lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'] + lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) def get_equip_item_codes_from_pkg_name(self, equip_pkg_name: str) -> Set[int]: """Helper function to provide the equip_item_codes (a set of ints) when provided with the equip_pkg_name of the equipment package""" - lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_code_lookups'] + lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] return set(lookup_df.loc[lookup_df["Equip_Pkg"] == equip_pkg_name, "Equip_Code"]) def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: @@ -597,7 +597,7 @@ class HealthSystem(Module): "Availability of beds. If 'default' then use the availability specified in the ResourceFile; if " "'none', then let no beds be ever be available; if 'all', then all beds are always available. NB. This " "parameter is over-ridden if an argument is provided to the module initialiser."), - 'equip_item_and_package_code_lookups': Parameter( + 'equip_item_and_package_lookups': Parameter( Types.DATA_FRAME, "Items based on the the HSSP III 1K Equipment Costing (SEL Costing Sheet): " "https://www.health.gov.mw/download/hssp-iii/, packages created in consultation with clinicians."), @@ -891,7 +891,7 @@ def read_parameters(self, data_folder): path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Bed_Capacity.csv') # Read in ResourceFile_Equipment - self.parameters['equip_item_and_package_code_lookups'] = pd.read_csv( + self.parameters['equip_item_and_package_lookups'] = pd.read_csv( path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Equipment.csv') # Data on the priority of each Treatment_ID that should be adopted in the queueing system according to different From 9dcf46bf8d8748f6d150b1f4853de8645c9f9974 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 29 Jan 2024 22:07:24 +0000 Subject: [PATCH 394/443] hs: ignore_unknown_equip_names --- src/tlo/methods/healthsystem.py | 116 +++++++++++++++++++++++++++----- 1 file changed, 98 insertions(+), 18 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 09521ce712..2b845a4b82 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -355,6 +355,45 @@ def get_equip_item_codes_from_pkg_name(self, equip_pkg_name: str) -> Set[int]: lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] return set(lookup_df.loc[lookup_df["Equip_Pkg"] == equip_pkg_name, "Equip_Code"]) + def ignore_unknown_equip_names(self, set_of_names: Set[str], type_in_set: str) -> Set[str]: + """Helper function to check if the equipment item or pkg names (depending on type_in_set: 'item' or 'pkg') from + the provided set are in the RF_Equipment. If they are not, they are added to a set to be warned about at the end + of the simulation. + + Only known (item or pkg) names are returned.""" + if set_of_names in [set(), None, {''}]: + return set() + + def add_unknown_names_to_dict(unknown_names_to_add: Set[str], dict_to_be_added_to: Dict) -> Dict: + if self.__class__.__name__ not in dict_to_be_added_to.keys(): + dict_to_be_added_to.update( + {self.__class__.__name__: unknown_names_to_add} + ) + else: + dict_to_be_added_to[self.__class__.__name__].update( + unknown_names_to_add + ) + return dict_to_be_added_to + + lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] + if type_in_set == "item": + unknown_names = set_of_names.difference(set(lookup_df["Equip_Item"])) + if unknown_names: + self.sim.modules['HealthSystem']._equip_items_missing_in_RF = \ + add_unknown_names_to_dict( + unknown_names, self.sim.modules['HealthSystem']._equip_items_missing_in_RF + ) + + elif type_in_set == "pkg": + unknown_names = set_of_names.difference(set(lookup_df["Equip_Pkg"])) + if unknown_names: + self.sim.modules['HealthSystem']._equip_pkgs_missing_in_RF = \ + add_unknown_names_to_dict( + unknown_names, self.sim.modules['HealthSystem']._equip_pkgs_missing_in_RF + ) + + return set_of_names.difference(unknown_names) + def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: """Helper function to set essential equipment. @@ -367,7 +406,8 @@ def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: "equipment item names from ResourceFile_Equipment.csv." ) - if set_of_equip not in [set(), None, {''}]: + set_of_equip = self.ignore_unknown_equip_names(set_of_equip, "item") + if set_of_equip: equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) self.ESSENTIAL_EQUIPMENT = equip_codes else: @@ -385,9 +425,12 @@ def add_equipment(self, set_of_equip: Set[str]) -> None: "Argument to add_equipment should be a non-empty set of strings of " "equipment item names from ResourceFile_Equipment.csv." ) - # from the set of equip item names create a set of equip item codes - equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) - self.EQUIPMENT.update(equip_codes) + # from the set of equip item names create a set of equip item codes, ignore unknown equip names + # (ie not included in RF_Equipment) + set_of_equip = self.ignore_unknown_equip_names(set_of_equip, "item") + if set_of_equip: + equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) + self.EQUIPMENT.update(equip_codes) def add_equipment_from_pkg(self, set_of_pkgs: Set[str]) -> None: """Helper function to update equipment with equipment from pkg(s). @@ -401,9 +444,12 @@ def add_equipment_from_pkg(self, set_of_pkgs: Set[str]) -> None: "Argument to add_equipment_from_pkg should be a non-empty set of strings of " "equipment pkg names from ResourceFile_Equipment.csv." ) - # update EQUIPMENT with eqip item codes from equip pkgs with provided names - for pkg_name in set_of_pkgs: - self.EQUIPMENT.update(self.get_equip_item_codes_from_pkg_name(pkg_name)) + # update EQUIPMENT with eqip item codes from equip pkgs with provided names, ignore unknown equip names + # (ie not included in RF_Equipment) + set_of_pkgs = self.ignore_unknown_equip_names(set_of_pkgs, "pkg") + if set_of_pkgs: + for pkg_name in set_of_pkgs: + self.EQUIPMENT.update(self.get_equip_item_codes_from_pkg_name(pkg_name)) def initialise(self): """Initialise the HSI: @@ -846,6 +892,10 @@ def __init__( self._hsi_event_names_missing_ess_equip = set() # The names of HSI events for which the settings of essential # equipment is missing. + self._equip_items_missing_in_RF = dict() # The equipment item names called for an HSI event, but are missing in + # the RF_Equipment. + self._equip_pkgs_missing_in_RF = dict() # The equipment pkg names called for an HSI event, but are missing in + # the RF_Equipment. def read_parameters(self, data_folder): @@ -1034,19 +1084,49 @@ def on_simulation_end(self): } ) - if self._hsi_event_names_missing_ess_equip: - hsi_event_names_missing_ess_equip = sorted(self._hsi_event_names_missing_ess_equip) - warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential " - f"equipment is missing:/n" - f"{hsi_event_names_missing_ess_equip}")) + if self._hsi_event_names_missing_ess_equip: + hsi_event_names_missing_ess_equip = sorted(self._hsi_event_names_missing_ess_equip) + warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential " + f"equipment is missing:/n" + f"{hsi_event_names_missing_ess_equip}")) + logger_summary.info( + key="hsi_event_names_missing_ess_equip", + data={"event_names": hsi_event_names_missing_ess_equip} + ) + # TODO: smt odd is going on, some hsi events were logged, according to my equipment_catalogue script, + # for which the essential equipment is not define, but they are not included in this warning. + # E.g. HSI_BladderCancer_Investigation_Following_Blood_Urine, HSI_BladderCancer_StartTreatment, + # HSI_BreastCancer_Investigation_Following_breast_lump_discernible, ... + + def sort_dict_for_print(dict_to_sort: Dict) -> Dict: + sorted_list = sorted(dict_to_sort.items()) + sorted_dict = {} + for key, value in sorted_list: + sorted_dict[key] = sorted(value) + return sorted_dict + + if self._equip_items_missing_in_RF: + sorted_equip_items_missing_in_RF = sort_dict_for_print(self._equip_items_missing_in_RF) + warnings.warn(UserWarning(f"The equipment item names called for an HSI event, but missing in the " + f"RF_Equipment:/n" + f"{sorted_equip_items_missing_in_RF}")) + + for _hsi_event_name, _item_names in sorted_equip_items_missing_in_RF.items(): + logger_summary.info( + key="equip_items_missing_in_RF", + data={_hsi_event_name: _item_names} + ) + + if self._equip_pkgs_missing_in_RF: + sorted_equip_pkgs_missing_in_RF = sort_dict_for_print(self._equip_pkgs_missing_in_RF) + warnings.warn(UserWarning(f"The equipment pkg names called for an HSI event, but missing in the " + f"RF_Equipment:/n" + f"{sorted_equip_pkgs_missing_in_RF}")) + for _hsi_event_name, _pkg_names in sorted_equip_pkgs_missing_in_RF.items(): logger_summary.info( - key="hsi_event_names_missing_ess_equip", - data={"event_names": hsi_event_names_missing_ess_equip} + key="equip_pkgs_missing_in_RF", + data={_hsi_event_name: _pkg_names} ) - # TODO: smt odd is going on, some hsi events were logged, according to my equipment_catalogue script, - # for which the essential equipment is not define, but they are not included in this warning. - # E.g. HSI_BladderCancer_Investigation_Following_Blood_Urine, HSI_BladderCancer_StartTreatment, - # HSI_BreastCancer_Investigation_Following_breast_lump_discernible, ... def setup_priority_policy(self): From 0bda4d0b8b9944c1785c1c97d666328feef1e328 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 30 Jan 2024 16:35:15 +0000 Subject: [PATCH 395/443] hs: warning messages shortened --- src/tlo/methods/healthsystem.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 2b845a4b82..178d97c3fc 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1086,8 +1086,7 @@ def on_simulation_end(self): if self._hsi_event_names_missing_ess_equip: hsi_event_names_missing_ess_equip = sorted(self._hsi_event_names_missing_ess_equip) - warnings.warn(UserWarning(f"The HSI event names which were initialised but the settings of essential " - f"equipment is missing:/n" + warnings.warn(UserWarning(f"Missing settings of essential equipment for HSI events:/n" f"{hsi_event_names_missing_ess_equip}")) logger_summary.info( key="hsi_event_names_missing_ess_equip", @@ -1107,8 +1106,7 @@ def sort_dict_for_print(dict_to_sort: Dict) -> Dict: if self._equip_items_missing_in_RF: sorted_equip_items_missing_in_RF = sort_dict_for_print(self._equip_items_missing_in_RF) - warnings.warn(UserWarning(f"The equipment item names called for an HSI event, but missing in the " - f"RF_Equipment:/n" + warnings.warn(UserWarning(f"Equipment item names were not recognised:/n" f"{sorted_equip_items_missing_in_RF}")) for _hsi_event_name, _item_names in sorted_equip_items_missing_in_RF.items(): @@ -1119,8 +1117,7 @@ def sort_dict_for_print(dict_to_sort: Dict) -> Dict: if self._equip_pkgs_missing_in_RF: sorted_equip_pkgs_missing_in_RF = sort_dict_for_print(self._equip_pkgs_missing_in_RF) - warnings.warn(UserWarning(f"The equipment pkg names called for an HSI event, but missing in the " - f"RF_Equipment:/n" + warnings.warn(UserWarning(f"Equipment pkg names were not recognised:/n" f"{sorted_equip_pkgs_missing_in_RF}")) for _hsi_event_name, _pkg_names in sorted_equip_pkgs_missing_in_RF.items(): logger_summary.info( From fc273a0ce38b5bdde72e263514bf793dad642dcf Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Fri, 1 Mar 2024 18:49:50 +0000 Subject: [PATCH 396/443] RF_Equip: added Source_Equip_Item for Electrocardiogram --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index be89a2177f..da80c3c028 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3489419094ed7a37987358fba7734b45410cc77750b3e560708739bef2536ba5 -size 33268 +oid sha256:d4ded21bb2e84bc43de26d8d2eed50a183d8445c08fff3019b0fd647932dc20d +size 33323 From 0289af3ac4848e28d9365e6a5fa898f2702d42e2 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 5 Mar 2024 23:27:36 +0000 Subject: [PATCH 397/443] RF_Cons_Items_and_Pkgs: reverted changes from script; Cystoscope, Endoscope, PSA test added manually --- .../ResourceFile_Consumables_Items_and_Packages.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/consumables/ResourceFile_Consumables_Items_and_Packages.csv b/resources/healthsystem/consumables/ResourceFile_Consumables_Items_and_Packages.csv index ef6bcbda8e..dd855930bf 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:144511462756e1db1eeab2745d04ccca9a89c93499b5fb3d493d1dba1732f22b -size 249275 +oid sha256:153332e93cf11d2435cf9f3d0cd4fcca8b405785b75a02757a6d19425f62318c +size 253579 From bdb755cfafecac6d046a317ccddb633d31925d1d Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Wed, 6 Mar 2024 23:00:21 +0000 Subject: [PATCH 398/443] modules: tidy up --- src/tlo/methods/cardio_metabolic_disorders.py | 1 + src/tlo/methods/diarrhoea.py | 1 - src/tlo/methods/measles.py | 2 +- src/tlo/methods/schisto.py | 6 +++--- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index 7ff996fac2..fbc2a36dfb 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1460,6 +1460,7 @@ def do_for_each_condition(self, _c) -> bool: 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}', hsi_event=self diff --git a/src/tlo/methods/diarrhoea.py b/src/tlo/methods/diarrhoea.py index 03e71a19cb..a7b3c7d843 100644 --- a/src/tlo/methods/diarrhoea.py +++ b/src/tlo/methods/diarrhoea.py @@ -736,7 +736,6 @@ def do_treatment(self, person_id, hsi_event): # STEP ONE: Aim to alleviate dehydration: prob_remove_dehydration = 0.0 if is_in_patient: - if hsi_event.get_consumables(item_codes=self.consumables_used_in_hsi['Treatment_Severe_Dehydration']): # In-patient receiving IV fluids (WHO Plan C) prob_remove_dehydration = \ diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index 34f4735ef6..f79155a200 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -418,7 +418,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) # initialise empty set + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): logger.debug(key="HSI_Measles_Treatment", diff --git a/src/tlo/methods/schisto.py b/src/tlo/methods/schisto.py index 9d69c9cce9..372d88205c 100644 --- a/src/tlo/methods/schisto.py +++ b/src/tlo/methods/schisto.py @@ -899,7 +899,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Schisto_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD' if under_5 else 'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) # initialise empty set + self.set_equipment_essential_to_run_event({''}) self._num_occurrences = 0 def apply(self, person_id, squeeze_factor): @@ -960,7 +960,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Schisto_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD' if under_5 else 'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) # initialise empty set + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """Do the treatment for this person.""" @@ -991,7 +991,7 @@ def __init__(self, module, person_id, beneficiaries_ids: Optional[Sequence] = No # `self.EXPECTED_APPT_FOOTPRINT` show that this requires 1 * that appointment type. self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) # initialise empty set + self.set_equipment_essential_to_run_event({''}) def apply(self, person_id, squeeze_factor): """Provide the treatment to the beneficiaries of this HSI.""" From bed404dfc3395e8ef5d1496515159ce061fa4738 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Thu, 7 Mar 2024 09:30:18 +0000 Subject: [PATCH 399/443] fix merge error in care_of_women_during_pregnancy.py --- src/tlo/methods/care_of_women_during_pregnancy.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 7e4c2a5907..48984f9926 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1098,12 +1098,11 @@ def interventions_delivered_each_visit_from_anc2(self, hsi_event): self.balance_energy_and_protein_supplementation(hsi_event=hsi_event) self.calcium_supplementation(hsi_event=hsi_event) - def check_anc1_can_run(self, individual_id, squeeze_factor, gest_age_next_contact): + def check_anc1_can_run(self, individual_id, gest_age_next_contact): """ This function is called by the first ANC contact and runs a series of checks to determine if the HSI should run on the date it has been scheduled for :param individual_id: individual id - :param squeeze_factor: squeeze_factor of the HSI calling this function :param gest_age_next_contact: gestational age, in weeks, this woman is due to return for her next ANC :returns True/False as to whether the event can run """ @@ -1151,15 +1150,13 @@ def check_anc1_can_run(self, individual_id, squeeze_factor, gest_age_next_contac return True - def check_subsequent_anc_can_run(self, individual_id, this_contact, this_visit_number, squeeze_factor, + def check_subsequent_anc_can_run(self, individual_id, this_visit_number, gest_age_next_contact): """ This function is called by the subsequent ANC contacts and runs a series of checks to determine if the HSI should run on the date it has been scheduled for :param individual_id: individual id - :param this_contact: HSI object of the current ANC contact that needs to be rebooked :param this_visit_number: Number of the next ANC contact in the schedule - :param squeeze_factor: squeeze_factor of the HSI calling this function :param gest_age_next_contact: gestational age, in weeks, this woman is due to return for her next ANC :returns True/False as to whether the event can run """ From 7c69972011b3524d0635742c144d01f447a6a1d3 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 7 Mar 2024 21:17:56 +0000 Subject: [PATCH 400/443] ac & hs: finish merge fix --- src/tlo/methods/care_of_women_during_pregnancy.py | 12 ++++-------- src/tlo/methods/healthsystem.py | 1 + 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 48984f9926..8bd5993d29 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -139,9 +139,6 @@ def __init__(self, name=None, resourcefilepath=None): 'specificity_blood_test_syphilis': Parameter( Types.LIST, 'specificity of a blood test to detect syphilis'), - 'squeeze_threshold_for_delay_three_an': Parameter( - Types.LIST, 'squeeze factor value over which an individual within a antenatal HSI is said to experience ' - 'type 3 delay i.e. delay in receiving appropriate care'), } PROPERTIES = { @@ -1150,8 +1147,7 @@ def check_anc1_can_run(self, individual_id, gest_age_next_contact): return True - def check_subsequent_anc_can_run(self, individual_id, this_visit_number, - gest_age_next_contact): + def check_subsequent_anc_can_run(self, individual_id, this_visit_number, gest_age_next_contact): """ This function is called by the subsequent ANC contacts and runs a series of checks to determine if the HSI should run on the date it has been scheduled for @@ -1267,9 +1263,9 @@ def initiate_maintenance_anti_hypertensive_treatment(self, individual_id, hsi_ev df = self.sim.population.props # Calculate the approximate dose for the remainder of pregnancy and check availability - dose = self.get_approx_days_of_pregnancy(individual_id) * 4 - cons = {_i: dose for _i in self.item_codes_preg_consumables['oral_antihypertensives']} - avail = hsi_event.get_consumables(item_codes=cons) + avail = pregnancy_helper_functions.return_cons_avail( + self, hsi_event, self.item_codes_preg_consumables, core='oral_antihypertensives', + number=(self.get_approx_days_of_pregnancy(individual_id) * 4)) # If the consumables are available then the woman is started on treatment if avail: diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 5e53d19ca4..d5ec1eafe8 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -521,6 +521,7 @@ def as_namedtuple( equipment=(tuple(sorted(self.EQUIPMENT))) ) + class HSIEventWrapper(Event): """This is wrapper that contains an HSI event. From 0d387a4f872830225d6417a40d7317c339b60553 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 21 Mar 2024 14:10:34 +0000 Subject: [PATCH 401/443] co: example of setting ess. equip based on condition --- src/tlo/methods/contraception.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index b672407c3b..a8e0ac680d 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1109,7 +1109,20 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level - self.set_equipment_essential_to_run_event({''}) + + # Set essential equipment based on the contraception method + if new_contraceptive == 'female_sterilization': + self.set_equipment_essential_to_run_event({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' + }) + # + 'Minor Surgery' pkg + # TODO: How to set pkg as essential? + elif new_contraceptive == 'IUD': + self.set_equipment_essential_to_run_event({ + 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' + }) + else: + self.set_equipment_essential_to_run_event({''}) @property def EXPECTED_APPT_FOOTPRINT(self): From b87781e45bfc6e9ed2a43e7057730ceb57f0b808 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 21 Mar 2024 14:11:09 +0000 Subject: [PATCH 402/443] co: comment updated; TODO added --- src/tlo/methods/contraception.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index a8e0ac680d..581969c0b7 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1159,6 +1159,7 @@ def apply(self, person_id, squeeze_factor): 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 + # TODO: Always or only if it's not a rescheduled appt? self.add_equipment({ 'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine' }) @@ -1215,7 +1216,7 @@ def apply(self, person_id, squeeze_factor): _new_contraceptive = self.new_contraceptive - # Update equipment if any needed for the method + # Add equipment if any used with the method if _new_contraceptive == 'female_sterilization': self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' From 88632f6da3dc3efc20ac0ea8b588696102341ed0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 21 Mar 2024 14:17:55 +0000 Subject: [PATCH 403/443] co, hs, RF_Equip, RF_HS_params, test_alri, test_co, test_hs: checking equip availability (before/within HSI); avail switcher; dummy probs by fac_level = 0.5; tests updated; TODOs added --- .../ResourceFile_HealthSystem_parameters.csv | 4 +- .../ResourceFile_Equipment.csv | 4 +- src/tlo/methods/contraception.py | 5 + src/tlo/methods/healthsystem.py | 107 ++++++-- tests/test_alri.py | 69 +++--- tests/test_contraception.py | 230 +++++++++++++++--- tests/test_healthsystem.py | 3 + 7 files changed, 327 insertions(+), 95 deletions(-) diff --git a/resources/healthsystem/ResourceFile_HealthSystem_parameters.csv b/resources/healthsystem/ResourceFile_HealthSystem_parameters.csv index 2828a9376c..ed560fb93e 100644 --- a/resources/healthsystem/ResourceFile_HealthSystem_parameters.csv +++ b/resources/healthsystem/ResourceFile_HealthSystem_parameters.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e36cbd225191f893f2de5f0f34adc251046af5ec58daaf7c86b09a6c83c1e31d -size 379 +oid sha256:b3666c302a8e0eb15fee119a6edd777ab21b602e8847b2bbd9357691c69e622a +size 406 diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 90012472cb..17c3f82ba7 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:8b5c09ef0800c69ed916edab8bf469ce83c47a09e1e75c7263a44c24f692ed3f -size 33197 +oid sha256:0be4639d20a28222ecba14f04025c0270fd799e13195ccf76c937448c4c5e39a +size 42931 diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 581969c0b7..33baabfa03 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -91,6 +91,7 @@ class Contraception(Module): 'max_number_of_runs_of_hsi_if_consumable_not_available': Parameter( Types.INT, "The maximum number of time an HSI can run (repeats occur if the consumables are not " "available)."), + # TODO: We don't have anything like this for equipment, should we? 'max_days_delay_between_decision_to_change_method_and_hsi_scheduled': Parameter( Types.INT, "The maximum delay (in days) between the decision for a contraceptive to change and the `topen` " @@ -1245,6 +1246,10 @@ def apply(self, person_id, squeeze_factor): # If the intended change was not possible due to non-available consumable, reschedule the appointment if (not co_administrated) and ( + # TODO: the max nmb of runs is set to 1000, so they will be coming back every day for 1000 consecutive days? + # -- but only 8 footprints for did_not_run event when switch to f. steril when consumables available but + # equipment not available in test_contraception (test_record_of_appt_footprint_for_switching_to_methods). + # What am I missing? ???? self._number_of_times_run < self.module.parameters['max_number_of_runs_of_hsi_if_consumable_not_available'] ): self.reschedule() diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 178d97c3fc..73aff18988 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -346,13 +346,13 @@ def make_appt_footprint(self, dict_of_appts): def get_equip_item_code_from_item_name(self, equip_item_name: str) -> int: """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" - lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] + lookup_df = self.sim.modules['HealthSystem'].parameters['Equipment'] return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) def get_equip_item_codes_from_pkg_name(self, equip_pkg_name: str) -> Set[int]: """Helper function to provide the equip_item_codes (a set of ints) when provided with the equip_pkg_name of the equipment package""" - lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] + lookup_df = self.sim.modules['HealthSystem'].parameters['Equipment'] return set(lookup_df.loc[lookup_df["Equip_Pkg"] == equip_pkg_name, "Equip_Code"]) def ignore_unknown_equip_names(self, set_of_names: Set[str], type_in_set: str) -> Set[str]: @@ -375,7 +375,7 @@ def add_unknown_names_to_dict(unknown_names_to_add: Set[str], dict_to_be_added_t ) return dict_to_be_added_to - lookup_df = self.sim.modules['HealthSystem'].parameters['equip_item_and_package_lookups'] + lookup_df = self.sim.modules['HealthSystem'].parameters['Equipment'] if type_in_set == "item": unknown_names = set_of_names.difference(set(lookup_df["Equip_Item"])) if unknown_names: @@ -391,7 +391,7 @@ def add_unknown_names_to_dict(unknown_names_to_add: Set[str], dict_to_be_added_t add_unknown_names_to_dict( unknown_names, self.sim.modules['HealthSystem']._equip_pkgs_missing_in_RF ) - + # TODO: What happens if all equip in set_of_names has unknown name? return set_of_names.difference(unknown_names) def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: @@ -451,6 +451,12 @@ def add_equipment_from_pkg(self, set_of_pkgs: Set[str]) -> None: for pkg_name in set_of_pkgs: self.EQUIPMENT.update(self.get_equip_item_codes_from_pkg_name(pkg_name)) + def get_essential_equip_availability(self, set_of_pkgs: Set[str]) -> bool: + # TODO: Or, should it be called set_essential_equip_and_get_availability to be more transparent about what the + # fnc does? + self.set_equipment_essential_to_run_event(set_of_pkgs) + return self.sim.modules['HealthSystem'].get_essential_equip_availability(self.ESSENTIAL_EQUIPMENT) + def initialise(self): """Initialise the HSI: * Set the facility_info @@ -643,9 +649,13 @@ class HealthSystem(Module): "Availability of beds. If 'default' then use the availability specified in the ResourceFile; if " "'none', then let no beds be ever be available; if 'all', then all beds are always available. NB. This " "parameter is over-ridden if an argument is provided to the module initialiser."), - 'equip_item_and_package_lookups': Parameter( - Types.DATA_FRAME, "Items based on the the HSSP III 1K Equipment Costing (SEL Costing Sheet): " - "https://www.health.gov.mw/download/hssp-iii/, packages created in consultation with clinicians."), + 'Equipment': Parameter( + Types.DATA_FRAME, "Data on equipment items, packages, and availability probabilities by facility level."), + 'equip_availability': Parameter( + Types.STRING, + "Availability of equipment. If 'default' then use the availability specified in the ResourceFile;" + " if 'none', then let no equipment ever be available; if 'all', then all equipment is always available. NB." + " This parameter is over-ridden if an argument is provided to the module initialiser."), # Service Availability 'Service_Availability': Parameter( @@ -719,6 +729,7 @@ def __init__( mode_appt_constraints: Optional[int] = None, cons_availability: Optional[str] = None, beds_availability: Optional[str] = None, + equip_availability: Optional[str] = None, randomise_queue: bool = True, ignore_priority: bool = False, policy_name: Optional[str] = None, @@ -743,6 +754,8 @@ def __init__( or 'none', requests for consumables are not logged. :param beds_availability: If 'default' then use the availability specified in the ResourceFile; if 'none', then let no beds be ever be available; if 'all', then all beds are always available. + :param equip_availability: If 'default' then use the availability specified in the ResourceFile; if 'none', then + let no equipment ever be available; if 'all', then all equipment is always available. :param randomise_queue ensure that the queue is not model-dependent, i.e. properly randomised for equal topen and priority :param ignore_priority: If ``True`` do not use the priority information in HSI @@ -835,13 +848,17 @@ def __init__( self.HSI_EVENT_QUEUE = [] self.hsi_event_queue_counter = 0 # Counter to help with the sorting in the heapq - # Store the argument provided for cons_availability + # Store the arguments provided for cons/beds/equip_availability assert cons_availability in (None, 'default', 'all', 'none') self.arg_cons_availability = cons_availability assert beds_availability in (None, 'default', 'all', 'none') self.arg_beds_availability = beds_availability + assert equip_availability in (None, 'default', 'all', 'none') + self.arg_equip_availability = equip_availability + self.equip_availability = 'all' # provided so that there is a default even before simulation is run + # `compute_squeeze_factor_to_district_level` is a Boolean indicating whether the computation of squeeze_factors # should be specific to each district (when `True`), or if the computation of squeeze_factors should be on the # basis that resources from all districts can be effectively "pooled" (when `False). @@ -941,7 +958,7 @@ def read_parameters(self, data_folder): path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Bed_Capacity.csv') # Read in ResourceFile_Equipment - self.parameters['equip_item_and_package_lookups'] = pd.read_csv( + self.parameters['Equipment'] = pd.read_csv( path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Equipment.csv') # Data on the priority of each Treatment_ID that should be adopted in the queueing system according to different @@ -958,7 +975,6 @@ def read_parameters(self, data_folder): sheet_name=None # all sheets read in ) - def pre_initialise_population(self): """Generate the accessory classes used by the HealthSystem and pass to them the data that has been read.""" @@ -994,6 +1010,9 @@ def pre_initialise_population(self): availability=self.get_cons_availability() ) + # Determine equip_availability + self.equip_availability = self.get_equip_availability() + self.tclose_overwrite = self.parameters['tclose_overwrite'] self.tclose_days_offset_overwrite = self.parameters['tclose_days_offset_overwrite'] @@ -1151,8 +1170,6 @@ def setup_priority_policy(self): def process_human_resources_files(self, use_funded_or_actual_staffing: str): """Create the data-structures needed from the information read into the parameters.""" - - # * Define Facility Levels self._facility_levels = set(self.parameters['Master_Facilities_List']['Facility_Level']) - {'5'} assert self._facility_levels == {'0', '1a', '1b', '2', '3', '4'} # todo soft code this? @@ -1457,6 +1474,42 @@ def get_beds_availability(self) -> str: return _beds_availability + def get_equip_availability(self) -> str: + """Returns equipment availability. (Should be equal to what is specified by the parameter, but overwrite with + what was provided in argument if an argument was specified -- provided for backward compatibility/debugging.)""" + + if self.arg_equip_availability is None: + _equip_availability = self.parameters['equip_availability'] + else: + _equip_availability = self.arg_equip_availability + + # Log the equip_availability + logger.info(key="message", + data=f"Running Health System With the Following Equipment Availability: " + f"{_equip_availability}" + ) + + return _equip_availability + + def get_equip_item_availability(self, equip_item_code: str) -> bool: + # TODO: update with implementation of essential equipment availability for the HSI event to run + # for now, always available + if equip_item_code in [243, 41]: # 243 = Pulse oximeter, 41 = 'Lamp, Anglepoise' + return False + return True # True of False + + def get_essential_equip_availability(self, essential_equip_set: Set[int]) -> bool: + if self.equip_availability == 'all': + # Always all equipment available + return True + elif self.equip_availability == 'default': + # True if all items of essential equipment available; False if any unavailable + return all(self.get_equip_item_availability(item_code) for item_code in essential_equip_set) + else: # self.equip_availability == 'none': + # True if no essential equipment requested, otherwise False as assumed no equipment available + # TODO: Should no equipment be logged then? + return not bool(essential_equip_set) + def schedule_to_call_never_ran_on_date(self, hsi_event: 'HSI_Event', tdate: datetime.datetime): """Function to schedule never_ran being called on a given date""" self.sim.schedule_event(HSIEventWrapper(hsi_event=hsi_event, run_hsi=False), tdate) @@ -2285,8 +2338,7 @@ def run_individual_level_events_in_mode_0_or_1(self, # Mode 0: All HSI Event run, with no squeeze # Mode 1: All HSI Events run with squeeze provided latter is not inf - ok_to_run = True - + ok_to_run = self.get_essential_equip_availability(event.ESSENTIAL_EQUIPMENT) if self.mode_appt_constraints == 1 and squeeze_factor == float('inf'): ok_to_run = False @@ -2624,18 +2676,25 @@ def process_events_mode_2(self, hold_over: List[HSIEventQueueItem]) -> None: # based on queue information, and we assume no squeeze ever takes place. squeeze_factor = 0. - # Check if any of the officers required have run out. - out_of_resources = False - for officer, call in original_call.items(): - # If any of the officers are not available, then out of resources - if officer not in set_capabilities_still_available: - out_of_resources = True + # Check if all essential equipment available and the officers required available. + ok_to_run = \ + self.module.sim.modules['HealthSystem'].get_essential_equip_availability( + next_event_tuple.hsi_event.ESSENTIAL_EQUIPMENT + ) # True if all essential equipment available + + if ok_to_run: + for officer, call in original_call.items(): + # If any of the officers are not available, we are out of resources, hence not ok_to_run + if officer not in set_capabilities_still_available: + ok_to_run = False + if not ok_to_run: + break # If officers still available, run event. Note: in current logic, a little # overtime is allowed to run last event of the day. This seems more realistic # than medical staff leaving earlier than # planned if seeing another patient would take them into overtime. - if out_of_resources: + if not ok_to_run: # Do not run, # Call did_not_run for the hsi_event @@ -2660,7 +2719,7 @@ def process_events_mode_2(self, hold_over: List[HSIEventQueueItem]) -> None: priority=_priority ) - # Have enough capabilities left to run event + # Have enough capabilities left to run event, ie ok_to_run else: # Notes-to-self: Shouldn't this be done after checking the footprint? # Compute the bed days that are allocated to this HSI and provide this @@ -3011,6 +3070,7 @@ class HealthSystemChangeParameters(Event, PopulationScopeEventMixin): * `capabilities_coefficient` * `cons_availability` * `beds_availability` + * `equip_availability` Note that no checking is done here on the suitability of values of each parameter.""" def __init__(self, module: HealthSystem, parameters: Dict): @@ -3037,6 +3097,9 @@ def apply(self, population): if 'beds_availability' in self._parameters: self.module.bed_days.availability = self._parameters['beds_availability'] + if 'equip_availability' in self._parameters: + self.module.equip_availability = self._parameters['equip_availability'] + class DynamicRescalingHRCapabilities(RegularEvent, PopulationScopeEventMixin): """ This event exists to scale the daily capabilities assumed at fixed time intervals""" diff --git a/tests/test_alri.py b/tests/test_alri.py index a98d2f277c..0fba5fea8d 100644 --- a/tests/test_alri.py +++ b/tests/test_alri.py @@ -54,7 +54,7 @@ def _get_person_id(df, age_bounds: tuple = (0.0, np.inf)) -> int: ].index[0] -def get_sim(tmpdir, seed, cons_available): +def get_sim(tmpdir, seed, cons_available, equip_available='all'): """Return simulation objection with Alri and other necessary modules registered.""" sim = Simulation( start_date=start_date, @@ -77,7 +77,8 @@ def get_sim(tmpdir, seed, cons_available): healthseekingbehaviour.HealthSeekingBehaviour(resourcefilepath=resourcefilepath), healthburden.HealthBurden(resourcefilepath=resourcefilepath), healthsystem.HealthSystem(resourcefilepath=resourcefilepath, - cons_availability=cons_available), + cons_availability=cons_available, + equip_availability=equip_available), alri.Alri(resourcefilepath=resourcefilepath, log_indivdual=0, do_checks=True), AlriPropertiesOfOtherModules(), ) @@ -85,10 +86,10 @@ def get_sim(tmpdir, seed, cons_available): @pytest.fixture -def sim_hs_all_consumables(tmpdir, seed): +def sim_hs_all_consumables_and_equipment(tmpdir, seed): """Return simulation objection with Alri and other necessary modules registered. All consumables available""" - return get_sim(tmpdir=tmpdir, seed=seed, cons_available='all') + return get_sim(tmpdir=tmpdir, seed=seed, cons_available='all', equip_available='all') @pytest.fixture @@ -127,17 +128,17 @@ def sim_hs_default_consumables(tmpdir, seed): return sim -def check_dtypes(sim_hs_all_consumables): - sim = sim_hs_all_consumables +def check_dtypes(sim_hs_all_consumables_and_equipment): + sim = sim_hs_all_consumables_and_equipment # Check types of columns df = sim.population.props orig = sim.population.new_row assert (df.dtypes == orig.dtypes).all() -def test_integrity_of_linear_models(sim_hs_all_consumables): +def test_integrity_of_linear_models(sim_hs_all_consumables_and_equipment): """Run the models to make sure that is specified correctly and can run.""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment sim.make_initial_population(n=5000) alri_module = sim.modules['Alri'] df = sim.population.props @@ -322,21 +323,21 @@ def test_integrity_of_linear_models(sim_hs_all_consumables): assert isinstance(res, float) and (res is not None) and (0.0 <= res <= 1.0), f"Problem with: {kwargs=}" -def test_basic_run(sim_hs_all_consumables): +def test_basic_run(sim_hs_all_consumables_and_equipment): """Short run of the module using default parameters with check on dtypes""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment dur = pd.DateOffset(months=1) popsize = 100 sim.make_initial_population(n=popsize) sim.simulate(end_date=start_date + dur) - check_dtypes(sim_hs_all_consumables) + check_dtypes(sim_hs_all_consumables_and_equipment) @pytest.mark.slow -def test_basic_run_lasting_two_years(sim_hs_all_consumables): +def test_basic_run_lasting_two_years(sim_hs_all_consumables_and_equipment): """Check logging results in a run of the model for two years, including HSI, with daily property config checking""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment dur = pd.DateOffset(years=2) popsize = 5000 @@ -362,9 +363,9 @@ def test_basic_run_lasting_two_years(sim_hs_all_consumables): assert set(log_one_person.columns) == set(sim.modules['Alri'].PROPERTIES.keys()) -def test_alri_polling(sim_hs_all_consumables): +def test_alri_polling(sim_hs_all_consumables_and_equipment): """Check polling events leads to incident cases""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment # get simulation object: popsize = 100 @@ -386,10 +387,10 @@ def test_alri_polling(sim_hs_all_consumables): assert len([q for q in sim.event_queue.queue if isinstance(q[3], AlriIncidentCase)]) > 0 -def test_nat_hist_recovery(sim_hs_all_consumables): +def test_nat_hist_recovery(sim_hs_all_consumables_and_equipment): """Check: Infection onset --> recovery""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment popsize = 100 sim.make_initial_population(n=popsize) @@ -466,9 +467,9 @@ def __will_die_of_alri(**kwargs): assert 0 == sim.modules['Alri'].logging_event.trackers['cured_cases'].report_current_total() -def test_nat_hist_death(sim_hs_all_consumables): +def test_nat_hist_death(sim_hs_all_consumables_and_equipment): """Check: Infection onset --> death""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment popsize = 100 sim.make_initial_population(n=popsize) @@ -523,10 +524,10 @@ def __will_die_of_alri(**kwargs): assert 0 == sim.modules['Alri'].logging_event.trackers['cured_cases'].report_current_total() -def test_nat_hist_cure_if_recovery_scheduled(sim_hs_all_consumables): +def test_nat_hist_cure_if_recovery_scheduled(sim_hs_all_consumables_and_equipment): """Show that if a cure event is run before when a person was going to recover naturally, it cause the episode to end earlier.""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment popsize = 100 @@ -598,10 +599,10 @@ def death(**kwargs): assert 1 == sim.modules['Alri'].logging_event.trackers['cured_cases'].report_current_total() -def test_nat_hist_cure_if_death_scheduled(sim_hs_all_consumables): +def test_nat_hist_cure_if_death_scheduled(sim_hs_all_consumables_and_equipment): """Show that if a cure event is run before when a person was going to die, it cause the episode to end without the person dying.""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment popsize = 100 sim.make_initial_population(n=popsize) @@ -667,10 +668,10 @@ def death(**kwargs): assert 1 == sim.modules['Alri'].logging_event.trackers['cured_cases'].report_current_total() -def test_immediate_onset_complications(sim_hs_all_consumables): +def test_immediate_onset_complications(sim_hs_all_consumables_and_equipment): """Check that if probability of immediately onsetting complications is 100%, then a person has all those complications immediately onset""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment popsize = 100 sim.make_initial_population(n=popsize) @@ -712,11 +713,11 @@ def test_immediate_onset_complications(sim_hs_all_consumables): assert df.at[person_id, 'ri_SpO2_level'] != '>=93%' -def test_no_immediate_onset_complications(sim_hs_all_consumables): +def test_no_immediate_onset_complications(sim_hs_all_consumables_and_equipment): """Check that if probability of immediately onsetting complications is 0%, then a person has none of those complications immediately onset """ - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment popsize = 100 @@ -748,7 +749,7 @@ def test_no_immediate_onset_complications(sim_hs_all_consumables): assert not df.loc[person_id, complications_cols].any() -def test_classification_based_on_symptoms_and_imci(sim_hs_all_consumables): +def test_classification_based_on_symptoms_and_imci(sim_hs_all_consumables_and_equipment): """Check that `_get_disease_classification` gives the expected classification.""" def make_hw_assesement_perfect(sim): @@ -760,7 +761,7 @@ def make_hw_assesement_perfect(sim): p['sensitivity_of_classification_of_non_severe_pneumonia_facility_level2'] = 1.0 p['sensitivity_of_classification_of_severe_pneumonia_facility_level2'] = 1.0 - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment make_hw_assesement_perfect(sim) sim.make_initial_population(n=1000) hsi_alri_treatment = HSI_Alri_Treatment(sim.modules['Alri'], 0) @@ -846,9 +847,9 @@ def make_hw_assesement_perfect(sim): ), f"{_correct_imci_classification_on_symptoms=}" -def test_do_effects_of_alri_treatment(sim_hs_all_consumables): +def test_do_effects_of_alri_treatment(sim_hs_all_consumables_and_equipment): """Check that running `do_alri_treatment` can prevent a death from occurring.""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment popsize = 100 sim.make_initial_population(n=popsize) @@ -921,10 +922,10 @@ def test_do_effects_of_alri_treatment(sim_hs_all_consumables): assert 1 == sim.modules['Alri'].logging_event.trackers['cured_cases'].report_current_total() -def test_severe_pneumonia_referral_from_hsi_first_appts(sim_hs_all_consumables): +def test_severe_pneumonia_referral_from_hsi_first_appts(sim_hs_all_consumables_and_equipment): """Check that a person is scheduled a treatment HSI following a presentation at HSI_GenericFirstApptAtFacilityLevel0 with severe pneumonia.""" - sim = sim_hs_all_consumables + sim = sim_hs_all_consumables_and_equipment popsize = 100 sim.make_initial_population(n=popsize) @@ -1234,6 +1235,7 @@ def initialise_simulation(self, sim): healthsystem.HealthSystem(resourcefilepath=resourcefilepath, disable_and_reject_all=disable_and_reject_all, cons_availability='all', + equip_availability='all', ), alri.Alri(resourcefilepath=resourcefilepath), AlriPropertiesOfOtherModules(), @@ -1338,6 +1340,7 @@ def initialise_simulation(self, sim): healthburden.HealthBurden(resourcefilepath=resourcefilepath), healthsystem.HealthSystem(resourcefilepath=resourcefilepath, cons_availability='all', + equip_availability='all', ), alri.Alri(resourcefilepath=resourcefilepath), AlriPropertiesOfOtherModules(), diff --git a/tests/test_contraception.py b/tests/test_contraception.py index 388b834393..b11402bfc2 100644 --- a/tests/test_contraception.py +++ b/tests/test_contraception.py @@ -20,6 +20,7 @@ def run_sim(tmpdir, disable=False, healthsystem_disable_and_reject_all=False, consumables_available=True, + equipment_available=True, run=True, no_discontinuation=False, incr_prob_of_failure=False, @@ -54,6 +55,14 @@ def __check_dtypes(simulation): else: _cons_available = consumables_available + # Determine availability of equipment (True --> all available; False --> none available; other --> custom arg.) + if equipment_available is True: + _equip_available = 'all' + elif equipment_available is False: + _equip_available = 'none' + else: + _equip_available = equipment_available + resourcefilepath = Path(os.path.dirname(__file__)) / '../resources' start_date = Date(2010, 1, 1) @@ -80,6 +89,7 @@ def __check_dtypes(simulation): disable=disable, disable_and_reject_all=healthsystem_disable_and_reject_all, cons_availability=_cons_available, + equip_availability=_equip_available, ), # - modules for mechanistic representation of contraception -> pregnancy -> labour -> delivery etc. @@ -417,8 +427,11 @@ def test_record_of_appt_footprint_for_switching_to_methods(tmpdir, seed): """Check that the APPT_FOOTPRINTS recorded by the HealthSystem match the expectation: specifically, that the appointment depends on the nature of the switch and whether it is a reoccurrence.""" - def get_appt_footprints(switch_from, switch_to, consumables_available) -> List[str]: - """Return a list of the APPT_FOOTPRINTS that are logged for one person for a particular switch.""" + def get_appt_footprints_when_did_not_or_did_run( + switch_from, switch_to, consumables_available, equipment_available + ) -> List[str]: + """Return a list of the APPT_FOOTPRINTS that are logged for one person for a particular switch, for HSI events + that 1) did not run, 2) did run.""" person_id = 0 sim = run_sim(tmpdir, @@ -426,6 +439,7 @@ def get_appt_footprints(switch_from, switch_to, consumables_available) -> List[s use_healthsystem=True, disable=False, consumables_available=consumables_available, + equipment_available=equipment_available, no_changes_in_contraception=True, no_discontinuation=True, equalised_risk_of_preg=0.0, @@ -436,7 +450,7 @@ def get_appt_footprints(switch_from, switch_to, consumables_available) -> List[s # Set the person's initial sex, age and contraceptive method sim.population.props.at[person_id, 'sex'] = 'F' - sim.population.props.at[person_id, 'age_years'] = 25 + sim.population.props.at[person_id, 'age_years'] = 31 sim.population.props.at[person_id, 'co_contraception'] = switch_from # Schedule the initial HSI for the change @@ -450,49 +464,193 @@ def get_appt_footprints(switch_from, switch_to, consumables_available) -> List[s sim.simulate(end_date=sim.start_date + pd.DateOffset(months=1)) hsi_run = parse_log_file(sim.log_filepath, level=logging.DEBUG)["tlo.methods.healthsystem"]["HSI_Event"] - return hsi_run.loc[ - hsi_run.did_run + return (hsi_run.loc[ + ~hsi_run.did_run & (hsi_run['Person_ID'] == person_id) & (hsi_run['TREATMENT_ID'] == 'Contraception_Routine'), 'Number_By_Appt_Type_Code' - ].to_list() + ].to_list(), + hsi_run.loc[ + hsi_run.did_run + & (hsi_run['Person_ID'] == person_id) + & (hsi_run['TREATMENT_ID'] == 'Contraception_Routine'), 'Number_By_Appt_Type_Code' + ].to_list()) + + def assert_expected_conds_for_appt_footprints( + cond_did_not_run: list, cond_did_run: list, _switch_from: str, _switch_to: str + ) -> None: + """Returns true if footprints for did_not_run and did_run HSIs satisfy given conditions. + Possible Conditions: + ['appt_footprint_equal', 'appt_footprint']; + ['len_equal_0']; + ['len_greater_0_and_all_footprints_equal', 'appt_footprint']; + ['len_greater_1_and_first_footprint_equal_subsequent_blank', 'appt_footprint']; + ['none'] + """ + # print(f"\n{set_cons_avail=}") + # print(f"{set_equip_avail=}") + # print(f"{_switch_from=}") + # print(f"{_switch_to=}") + did_not_run_footprints, did_run_footprints = get_appt_footprints_when_did_not_or_did_run( + switch_from=_switch_from, switch_to=_switch_to, + consumables_available=set_cons_avail, equipment_available=set_equip_avail + ) + # Assert condition for did_not_run footprints + if cond_did_not_run[0] == 'len_equal_0': + assert 0 == len(did_not_run_footprints) + elif cond_did_not_run[0] == 'len_greater_0_and_all_footprints_equal': + assert 0 < len(did_not_run_footprints) and all([_x == cond_did_not_run[1] for _x in did_not_run_footprints]) + elif cond_did_not_run[0] == 'none': + assert [] == did_not_run_footprints + else: + warnings.warn(f'\nWarning: {cond_did_not_run=} does not exist.') + assert 0 + + # Assert condition for did_run footprints + if cond_did_run[0] == 'appt_footprint_equal': + assert [cond_did_run[1]] == did_run_footprints + elif cond_did_run[0] == 'len_greater_1_and_first_footprint_equal_subsequent_blank': + assert len(did_run_footprints) > 1 and did_run_footprints[0] == cond_did_run[1] and \ + (0 == len([_x for _i, _x in enumerate(did_run_footprints) if (_i != 0) and (_x != {})])) + elif cond_did_run[0] == 'none': + assert [] == did_run_footprints + else: + warnings.warn(f'\nWarning: {cond_did_run=} does not exist.') + assert 0 + + # 1) If both consumables and equipment available + set_cons_avail = True + set_equip_avail = True + # ... the HSI will only be run once, hence no footprint for did_not_run and exactly one footprint for did_run: + # - If switch to female_sterilization => 'MinorSurg' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'MinorSurg': 1}], + _switch_from='not_using', _switch_to='female_sterilization' + ) + # - If maintaining IUD => 'FamilyPlanning' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'FamPlan': 1}], + _switch_from='IUD', _switch_to='IUD' + ) + # - If switching to anything new => 'FamilyPlanning' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'FamPlan': 1}], + _switch_from='not_using', _switch_to='pill' + ) + # - If maintaining on implant => 'FamilyPlanning' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'FamPlan': 1}], + _switch_from='implant', _switch_to='implant' + ) + # - If maintaining on pill => 'PharmDispensing' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'PharmDispensing': 1}], + _switch_from='pill', _switch_to='pill' + ) - # 1) If consumables available, the HSI will only be run once: - # - If switch to female_sterilization => 'MinorSurg'" - assert [{'MinorSurg': 1}] == get_appt_footprints(switch_from='not_using', - switch_to='female_sterilization', - consumables_available=True) + # 2) If consumables available, but equipment not available + set_cons_avail = True + set_equip_avail = False + # ... when a method with essential equipment (f. sterilization or IUD) requested, the HSI will never run hence no + # appt_footprint will be returned for did_run HSI, but it will be returned for did_not_run HSI multiple times: + # - If switch to female_sterilization => 'MinorSurg' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_greater_0_and_all_footprints_equal', {'MinorSurg': 1}], cond_did_run=['none'], + _switch_from='not_using', _switch_to='female_sterilization' + ) + # - If maintaining IUD => 'FamilyPlanning' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_greater_0_and_all_footprints_equal', {'FamPlan': 1}], cond_did_run=['none'], + _switch_from='IUD', _switch_to='IUD' + ) + # ... otherwise the HSI will run and only once: # - If switching to anything new => 'FamilyPlanning' - assert [{'FamPlan': 1}] == get_appt_footprints(switch_from='not_using', - switch_to='pill', - consumables_available=True) + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'FamPlan': 1}], + _switch_from='not_using', _switch_to='pill' + ) # - If maintaining on implant => 'FamilyPlanning' - assert [{'FamPlan': 1}] == get_appt_footprints(switch_from='implant', - switch_to='implant', - consumables_available=True) + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'FamPlan': 1}], + _switch_from='implant', _switch_to='implant' + ) # - If maintaining on pill => 'PharmDispensing' - assert [{'PharmDispensing': 1}] == get_appt_footprints(switch_from='pill', - switch_to='pill', - consumables_available=True) - - # 2) If consumables not available... there should be multiple footprints, but only the first is non-blank. - def is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank(x): - return ( - (len(x) > 1) - & (x[0] != {}) - & (0 == len([_x for _i, _x in enumerate(x) if (_i != 0) and (_x != {})])) - ) + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'PharmDispensing': 1}], + _switch_from='pill', _switch_to='pill' + ) - assert is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank( - get_appt_footprints(switch_from='not_using', switch_to='female_sterilization', consumables_available=False) + # 3) If consumables not available, but equipment available + set_cons_avail = False + set_equip_avail = True + # ... it does run and there should be multiple footprints, but only the first is non-blank: + # - If switch to female_sterilization => 'MinorSurg' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['none'], + cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'MinorSurg': 1}], + _switch_from='not_using', _switch_to='female_sterilization' + ) + # - If maintaining IUD => 'FamilyPlanning' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['none'], + cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'FamPlan': 1}], + _switch_from='IUD', _switch_to='IUD' + ) + # - If switching to anything new => 'FamilyPlanning' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['none'], + cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'FamPlan': 1}], + _switch_from='not_using', _switch_to='pill' ) - assert is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank( - get_appt_footprints(switch_from='not_using', switch_to='pill', consumables_available=False) + # - If maintaining on implant => 'FamilyPlanning' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['none'], + cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'FamPlan': 1}], + _switch_from='implant', _switch_to='implant' + ) + # - If maintaining on pill => 'PharmDispensing' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['none'], + cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'PharmDispensing': 1}], + _switch_from='pill', _switch_to='pill' ) - assert is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank( - get_appt_footprints(switch_from='implant', switch_to='implant', consumables_available=False) + + # 4) If both consumables and equipment not available, ... + set_cons_avail = False + set_equip_avail = False + # ... when a method with essential equipment (f. sterilization or IUD) requested, the HSI will never run hence no + # appt_footprint will be returned for did_run HSI, but it will be returned for did_not_run HSI multiple times: + # - If switch to female_sterilization => 'MinorSurg' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_greater_0_and_all_footprints_equal', {'MinorSurg': 1}], cond_did_run=['none'], + _switch_from='not_using', _switch_to='female_sterilization' ) - assert is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank( - get_appt_footprints(switch_from='pill', switch_to='pill', consumables_available=False) + # - If maintaining IUD => 'FamilyPlanning' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['len_greater_0_and_all_footprints_equal', {'FamPlan': 1}], cond_did_run=['none'], + _switch_from='IUD', _switch_to='IUD' + ) + # ... otherwise the HSI will run and there should be multiple footprints, but only the first is non-blank: + # TODO: this is the current logic, but it doesn't sound right, if it is never performed due to missing consumables + # it should never have the footprint equal, or do we expect that they will be coming again and again and again + # and again ... until they will get it in very far future? + + # - If switching to anything new => 'FamilyPlanning' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['none'], + cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'FamPlan': 1}], + _switch_from='not_using', _switch_to='pill' + ) + # - If maintaining on implant => 'FamilyPlanning' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['none'], + cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'FamPlan': 1}], + _switch_from='implant', _switch_to='implant' + ) + # - If maintaining on pill => 'PharmDispensing' + assert_expected_conds_for_appt_footprints( + cond_did_not_run=['none'], + cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'PharmDispensing': 1}], + _switch_from='pill', _switch_to='pill' ) diff --git a/tests/test_healthsystem.py b/tests/test_healthsystem.py index 22b9d7ff8c..f85fd3e735 100644 --- a/tests/test_healthsystem.py +++ b/tests/test_healthsystem.py @@ -1260,6 +1260,7 @@ def test_HealthSystemChangeParameters(seed, tmpdir): 'capabilities_coefficient': 0.5, 'cons_availability': 'all', 'beds_availability': 'default', + 'equip_availability': 'default', } new_parameters = { 'mode_appt_constraints': 2, @@ -1267,6 +1268,7 @@ def test_HealthSystemChangeParameters(seed, tmpdir): 'capabilities_coefficient': 1.0, 'cons_availability': 'none', 'beds_availability': 'none', + 'equip_availability': 'all', } class CheckHealthSystemParameters(RegularEvent, PopulationScopeEventMixin): @@ -1282,6 +1284,7 @@ def apply(self, population): _params['capabilities_coefficient'] = hs.capabilities_coefficient _params['cons_availability'] = hs.consumables.cons_availability _params['beds_availability'] = hs.bed_days.availability + _params['equip_availability'] = hs.equip_availability logger = logging.getLogger('tlo.methods.healthsystem') logger.info(key='CheckHealthSystemParameters', data=_params) From a6fffc9fd2a1579371fbeb7f7f902c548613eecd Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:25:21 +0000 Subject: [PATCH 404/443] example test suite --- tests/test_equipment.py | 187 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 187 insertions(+) create mode 100644 tests/test_equipment.py diff --git a/tests/test_equipment.py b/tests/test_equipment.py new file mode 100644 index 0000000000..01aff8aab8 --- /dev/null +++ b/tests/test_equipment.py @@ -0,0 +1,187 @@ +"""This file contains all the tests to do with Equipment use logging and availability checks.""" +import os +from pathlib import Path +from typing import Union, Dict, Iterable + +import pandas as pd + +from tlo import Simulation, Module, Date +from tlo.analysis.utils import parse_log_file +from tlo.events import IndividualScopeEventMixin +from tlo.methods import Metadata, demography, healthsystem +from tlo.methods.hsi_event import HSI_Event + +resourcefilepath = Path(os.path.dirname(__file__)) / '../resources' + +equipment_item_code_that_is_available = [0, 1, ] +equipment_item_code_that_is_not_available = [2, 3, ] + + +def run_simulation_return_log(seed, tmpdir, essential_equipment: Iterable[str], other_equipment: Iterable[str]) -> Dict: + """Returns a parsed logs from `tlo.methods.healthsystem.summary` from a simulation object, in which a single + event has been scheduled with the specified equipment usage, and the availability of equipment has been manipulated. + """ + + class DummyHSIEvent(HSI_Event, IndividualScopeEventMixin): + def __init__(self, + module, + person_id, + level, + essential_equipment: Union[int, None], + other_equipment: Union[int, None] + ): + super().__init__(module, person_id=person_id) + self.TREATMENT_ID = "DummyHSIEvent" + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) + self.ACCEPTED_FACILITY_LEVEL = level + self.ESSENTIAL_EQUIPMENT = str(essential_equipment) if essential_equipment is not None else set() + self._other_equipment = other_equipment + + def apply(self, person_id, squeeze_factor): + if self._other_equipment is not None: + self.add_equipment(self._other_equipment) + + + class DummyModule(Module): + METADATA = {Metadata.DISEASE_MODULE, Metadata.USES_HEALTHSYSTEM} + + def __init__(self, essential_equipment, other_equipment, name=None): + super().__init__(name) + self.essential_equipment = essential_equipment + self.other_equipment = other_equipment + + def read_parameters(self, data_folder): + pass + + def initialise_population(self, population): + pass + + def initialise_simulation(self, sim): + # Schedule the HSI_Event to occur on the first day of the simulation + sim.modules['HealthSystem'].schedule_hsi_event( + hsi_event=DummyHSIEvent( + person_id=0, + level='2', + module=sim.modules['DummyModule'], + essential_equipment=self.essential_equipment, + other_equipment=self.other_equipment, + ), + do_hsi_event_checks=False, + topen=sim.date, + tclose=None, + priority=0, + ) + + log_config = {"filename": "log", "directory": tmpdir} + sim = Simulation(start_date=Date(2010, 1, 1), seed=seed, log_config=log_config) + sim.register( + demography.Demography(resourcefilepath=resourcefilepath), + healthsystem.HealthSystem(resourcefilepath=resourcefilepath), + DummyModule(essential_equipment=essential_equipment, other_equipment=other_equipment), + ) + + # Manipulate availability of equipment + df = sim.modules['HealthSystem'].parameters['Equipment'] + col_for_availability = df.columns[df.columns.str.startswith('Avail_')] + df.loc[df['Equip_Code'].isin(equipment_item_code_that_is_available), col_for_availability] = True + df.loc[df['Equip_Code'].isin(equipment_item_code_that_is_not_available), col_for_availability] = False + df['Equip_Item'] = df['Equip_Code'].astype(str) + sim.modules['HealthSystem'].parameters['Equipment'] = df.loc[df['Equip_Code'].isin(set(equipment_item_code_that_is_available) | set(equipment_item_code_that_is_not_available))] + + sim.make_initial_population(n=100) + sim.simulate(end_date=pd.DateOffset(months=1)) + + return parse_log_file(sim.log_filepath)['tlo.methods.healthsystem.summary'] + + + + +def test_equipment_use_is_logged(seed, tmpdir): + """Check that an HSI that after an HSI is run, the logs reflect the use of the equipment (and correctly record the + name of the HSI and the facility_level at which ran). + This is repeated for: + * An HSI that declares use of equipment during its `apply` method (but no essential equipment); + * An HSI that declare use of essential equipment but nothing in its `apply` method`; + * An HSI that declare use of essential equipment and equipment during its `apply` method; + * An HSI that declares not use of any equipment (logs should be empty). + """ + + def logged_equipment_used(sim: Dict) -> pd.DataFrame: + """Read the log to work out what equipment usage has been logged.""" + # @Eva - I think this will somehow use the function that is currently in `src/scripts/healthsystem/equipment/equipment_catalogue.py` + pass + + def get_sim(essential_equipment, other_equipment): + """Pass-through to `run_simulation_return_log` to make call simpler.""" + return run_simulation_return_log( + seed=seed, + tmpdir=tmpdir, + essential_equipment=essential_equipment, + other_equipment=other_equipment, + ) + + # Check that the log matches expectation under each permutation + item_available_as_set_of_str = {str(equipment_item_code_that_is_available[0])} + + # * An HSI that declares use of equipment during its `apply` method (but no essential equipment) + expected_df = pd.DataFrame() # <-- fill in what we expect it to look like + assert expected_df.equals(logged_equipment_used(get_sim( + essential_equipment={}, + other_equipment=item_available_as_set_of_str, + ))) + + # * An HSI that declare use of essential equipment but nothing in its `apply` method`; + expected_df = pd.DataFrame() # <-- fill in what we expect it to look like + assert expected_df.equals(logged_equipment_used(get_sim( + essential_equipment=item_available_as_set_of_str, + other_equipment={}, + ))) + + # * An HSI that declare use of essential equipment and equipment during its `apply` method; + expected_df = pd.DataFrame() # <-- fill in what we expect it to look like + assert expected_df.equals(logged_equipment_used(get_sim( + essential_equipment=item_available_as_set_of_str, + other_equipment=item_available_as_set_of_str, + ))) + + # * An HSI that declares not use of any equipment (logs should be empty). + expected_df = pd.DataFrame() # <-- fill in what we expect it to look like + assert expected_df.equals(logged_equipment_used(get_sim( + essential_equipment={}, + other_equipment={}, + ))) + + +def test_hsi_does_not_run_if_essential_equipment_is_not_available(seed, tmpdir): + """Check that an HSI which declares an item of equipment that is essential does run if that item is available + and does not run if that item is not available.""" + + def did_hsi_run(sim: Dict) -> bool: + """Read the log to work out if the Dummy HSI Event ran or not.""" + pass + + def get_sim(essential_equipment): + """Pass-through to `run_simulation_return_log` to make call simpler.""" + return run_simulation_return_log( + seed=seed, + tmpdir=tmpdir, + essential_equipment=essential_equipment, + other_equipment=None + ) + + # HSI_Event that requires equipment that is available --> will run + assert did_hsi_run( + get_sim( + essential_equipment=set(str(equipment_item_code_that_is_available[0])) + ) + ) + + # HSI_Event that requires equipment that is not available --> will not run + assert not did_hsi_run( + get_sim( + essential_equipment=set(str(equipment_item_code_that_is_not_available[0])) + ) + ) + + + From 8428d8ae194d5ccbf7e215a43bf50ff1218c3898 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:26:43 +0000 Subject: [PATCH 405/443] typo and add todo --- src/tlo/methods/hsi_event.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/hsi_event.py b/src/tlo/methods/hsi_event.py index ed3703338d..c7981f2dbc 100644 --- a/src/tlo/methods/hsi_event.py +++ b/src/tlo/methods/hsi_event.py @@ -334,14 +334,21 @@ def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: else: self.ESSENTIAL_EQUIPMENT = set() + # todo add function to set essential equipment + def add_equipment(self, set_of_equip: Set[str]) -> None: """Helper function to update equipment. Should be passed a set of equipment item names (strings). """ # Update EQUIPMENT if the given set_of_equip in correct format, ie a non-empty set of strings - if not isinstance(set_of_equip, set) or any(not isinstance(item, str) for item in set_of_equip) or \ - (set_of_equip in [set(), None, {''}]): + if ( + (not isinstance(set_of_equip, set)) + or + any(not isinstance(item, str) for item in set_of_equip) + or + (set_of_equip in [set(), None, {''}]) + ): raise ValueError( "Argument to add_equipment should be a non-empty set of strings of " "equipment item names from ResourceFile_Equipment.csv." From 2a3c8f8b6c45ac2fef51a8b48ba1f98652813471 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 25 Mar 2024 15:37:05 +0000 Subject: [PATCH 406/443] further tests --- tests/test_equipment.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/test_equipment.py b/tests/test_equipment.py index 01aff8aab8..3d15294c9a 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -184,4 +184,17 @@ def get_sim(essential_equipment): ) +def test_lookup_equipment_item_code_from_item_name(): + pass + +def test_lookup_equipment_item_code_from_pkg_name(): + pass + +def test_lookup_item_availability_by_hsi_event(): + pass + +def test_change_equipment_availability(): + """Test that we can change the availability of equipment midway through the simulation.""" + pass + From b1aa9aef99915ee20b3bf89219eba7210ab5c269 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Mon, 25 Mar 2024 16:04:42 +0000 Subject: [PATCH 407/443] [no_ci] RF_Equip: availabilities changed from probs to True/False values (all True for now) --- .../infrastructure_and_equipment/ResourceFile_Equipment.csv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv index 17c3f82ba7..5700c76b27 100644 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0be4639d20a28222ecba14f04025c0270fd799e13195ccf76c937448c4c5e39a -size 42931 +oid sha256:20c07f76ec100a3c1a221c4c8ecc4bd6e37379f395b08ae67491501d04f04d6e +size 45343 From ffd5178ea922fc350fca0aa26a9fb6d38e5f766e Mon Sep 17 00:00:00 2001 From: Eva Janouskova <48157464+EvaJanouskova@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:04:52 +0100 Subject: [PATCH 408/443] ac: rm comments Co-authored-by: Tim Hallett <39991060+tbhallett@users.noreply.github.com> --- src/tlo/methods/care_of_women_during_pregnancy.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index d70ad7a29b..bc22b86993 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -205,8 +205,6 @@ def get_and_store_pregnancy_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- BLOOD TEST EQUIPMENT --------------------------------------------------- - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing self.item_codes_preg_consumables['blood_test_equipment'] = \ get_list_of_items(self, ['Blood collecting tube, 5 ml', 'Cannula iv (winged with injection pot) 18_each_CMST', From 7c8ea54984dc4c72a94ee429afe25a762f1efd35 Mon Sep 17 00:00:00 2001 From: Eva Janouskova <48157464+EvaJanouskova@users.noreply.github.com> Date: Tue, 26 Mar 2024 19:20:54 +0100 Subject: [PATCH 409/443] hs: rm equip_availability before sim default Co-authored-by: Tim Hallett <39991060+tbhallett@users.noreply.github.com> --- src/tlo/methods/healthsystem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 1364f1196d..637a1b1327 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -440,7 +440,6 @@ def __init__( assert equip_availability in (None, 'default', 'all', 'none') self.arg_equip_availability = equip_availability - self.equip_availability = 'all' # provided so that there is a default even before simulation is run # `compute_squeeze_factor_to_district_level` is a Boolean indicating whether the computation of squeeze_factors # should be specific to each district (when `True`), or if the computation of squeeze_factors should be on the From 6ddd11e529719fe4198191d717075c57450657d0 Mon Sep 17 00:00:00 2001 From: Eva Janouskova <48157464+EvaJanouskova@users.noreply.github.com> Date: Tue, 26 Mar 2024 20:02:44 +0100 Subject: [PATCH 410/443] labour: rm comments Co-authored-by: Tim Hallett <39991060+tbhallett@users.noreply.github.com> --- src/tlo/methods/labour.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 5fdc3d3e52..42da5cc7d0 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -686,8 +686,6 @@ def get_and_store_labour_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing self.item_codes_lab_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', From 4f4d858b1085a373181e0ee24210f6d90114a832 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 26 Mar 2024 18:46:36 +0000 Subject: [PATCH 411/443] hs: rm extra line --- src/tlo/methods/healthsystem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 637a1b1327..a6c30c85a1 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -635,7 +635,6 @@ def pre_initialise_population(self): # Set up framework for considering a priority policy self.setup_priority_policy() - def initialise_population(self, population): self.bed_days.initialise_population(population.props) From f8b0ac1b9c9fd7ebb8b30630372109e5ca072bfe Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Tue, 26 Mar 2024 18:56:47 +0000 Subject: [PATCH 412/443] hs: raise error if 1) ess equip not a set of ints, 2) invalid equip_availability --- src/tlo/methods/healthsystem.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index a6c30c85a1..5dfff499e6 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1088,7 +1088,7 @@ def get_equip_availability(self) -> str: return _equip_availability - def get_equip_item_availability(self, equip_item_code: str) -> bool: + def get_equip_item_availability(self, equip_item_code: int) -> bool: # TODO: update with implementation of essential equipment availability for the HSI event to run # for now, always available if equip_item_code in [243, 41]: # 243 = Pulse oximeter, 41 = 'Lamp, Anglepoise' @@ -1096,16 +1096,22 @@ def get_equip_item_availability(self, equip_item_code: str) -> bool: return True # True of False def get_essential_equip_availability(self, essential_equip_set: Set[int]) -> bool: + if not isinstance(essential_equip_set, set) or any(not isinstance(item, int) for item in essential_equip_set): + raise ValueError( + "Argument to get_essential_equip_availability should be a set of integers." + ) if self.equip_availability == 'all': # Always all equipment available return True elif self.equip_availability == 'default': # True if all items of essential equipment available; False if any unavailable return all(self.get_equip_item_availability(item_code) for item_code in essential_equip_set) - else: # self.equip_availability == 'none': + elif self.equip_availability == 'none': # True if no essential equipment requested, otherwise False as assumed no equipment available # TODO: Should no equipment be logged then? return not bool(essential_equip_set) + else: + raise ValueError("Value for self.equip_availability invalid, it should be 'all', 'default', or 'none'.") def schedule_to_call_never_ran_on_date(self, hsi_event: 'HSI_Event', tdate: datetime.datetime): """Function to schedule never_ran being called on a given date""" From 16752aef35206117650f617ceec558d99eb17921 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Thu, 9 May 2024 10:24:22 +0100 Subject: [PATCH 413/443] roll back incidental changes Note to @Eva -- there were quite a ot of changes in `test_contraception.py` that seemed unrelated to this PR, but which she may like to cherry-pick and raise a PR for specifically. --- src/tlo/analysis/utils.py | 6 +- src/tlo/methods/alri.py | 4 - src/tlo/methods/breast_cancer.py | 3 +- src/tlo/methods/contraception.py | 44 +----- src/tlo/methods/newborn_outcomes.py | 2 - src/tlo/methods/rti.py | 4 +- tests/test_alri.py | 2 +- tests/test_contraception.py | 230 +++++----------------------- 8 files changed, 42 insertions(+), 253 deletions(-) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index 64ba966aa5..3aeff2bb11 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -18,7 +18,6 @@ import numpy as np import pandas as pd import squarify -from pandas.api.types import is_numeric_dtype from tlo import Date, Simulation, logging, util from tlo.logging.reader import LogData @@ -294,10 +293,7 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series: df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key] output_from_eval: pd.Series = generate_series(df) assert pd.Series == type(output_from_eval), 'Custom command does not generate a pd.Series' - if is_numeric_dtype(output_from_eval): - res[draw_run] = output_from_eval * get_multiplier(draw, run) - else: - res[draw_run] = output_from_eval + res[draw_run] = output_from_eval * get_multiplier(draw, run) except KeyError: # Some logs could not be found - probably because this run failed. diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index 447cef8026..277726e0ff 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2316,8 +2316,6 @@ def __init__(self, module: Module, person_id: int, facility_level: str = "0", in self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels - self.set_equipment_essential_to_run_event({'Pulse oximeter'}) - # TODO: CORRECT --- an example with ess. equipm. set (which may or may not be used at the end) self.is_followup_following_treatment_failure = is_followup_following_treatment_failure if not inpatient: @@ -2624,8 +2622,6 @@ def _get_disease_classification_for_treatment_decision(self, 'cough_or_cold' (symptoms-based assessment) }.""" - self.add_equipment({'Pulse oximeter'}) - child_is_younger_than_2_months = age_exact_years < (2.0 / 12.0) imci_classification_based_on_symptoms = self._get_imci_classification_based_on_symptoms( diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 677b413fd2..8a3281243c 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -668,7 +668,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - # TODO: but the appt footprints suggests mammography to be provided def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -685,7 +684,7 @@ def apply(self, person_id, squeeze_factor): if not pd.isnull(df.at[person_id, "brc_date_diagnosis"]): return hs.get_blank_appt_footprint() - df.at[person_id, 'brc_breast_lump_discernible_investigated'] = True + df.brc_breast_lump_discernible_investigated = True # Use a biopsy to diagnose whether the person has breast Cancer: # todo: request consumables needed for this diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 38baf6d9b9..67d6684fce 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -91,7 +91,6 @@ class Contraception(Module): 'max_number_of_runs_of_hsi_if_consumable_not_available': Parameter( Types.INT, "The maximum number of time an HSI can run (repeats occur if the consumables are not " "available)."), - # TODO: We don't have anything like this for equipment, should we? 'max_days_delay_between_decision_to_change_method_and_hsi_scheduled': Parameter( Types.INT, "The maximum delay (in days) between the decision for a contraceptive to change and the `topen` " @@ -1111,20 +1110,6 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level - # Set essential equipment based on the contraception method - if new_contraceptive == 'female_sterilization': - self.set_equipment_essential_to_run_event({ - 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' - }) - # + 'Minor Surgery' pkg - # TODO: How to set pkg as essential? - elif new_contraceptive == 'IUD': - self.set_equipment_essential_to_run_event({ - 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' - }) - else: - self.set_equipment_essential_to_run_event({''}) - @property def EXPECTED_APPT_FOOTPRINT(self): """Return the expected appt footprint based on contraception method and whether the HSI has been rescheduled.""" @@ -1159,12 +1144,6 @@ 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 - # TODO: Always or only if it's not a rescheduled appt? - self.add_equipment({ - 'Weighing scale', 'Height Pole (Stadiometer)', 'Blood pressure machine' - }) - # Determine essential and optional items # TODO: we don't distinguish essential X optional for contraception methods yet, will need to update once we do items_essential = self.module.cons_codes[self.new_contraceptive] @@ -1191,8 +1170,7 @@ 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 and update used equipment if any, if not set the contraception to - # "not_using": + # if so do log the availability of all items, 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: @@ -1216,22 +1194,6 @@ def apply(self, person_id, squeeze_factor): ) _new_contraceptive = self.new_contraceptive - - # Add equipment if any used with the method - if _new_contraceptive == 'female_sterilization': - self.add_equipment({ - 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' - }) - self.add_equipment_from_pkg({ - 'Minor Surgery' - }) - # TODO: this is just an example - update once figured out what we want in the pkgs - # (! Update also the RF_Equipment accordingly !) - elif _new_contraceptive == 'IUD': - self.add_equipment({ - 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' - }) - else: _new_contraceptive = "not_using" @@ -1246,10 +1208,6 @@ def apply(self, person_id, squeeze_factor): # If the intended change was not possible due to non-available consumable, reschedule the appointment if (not co_administrated) and ( - # TODO: the max nmb of runs is set to 1000, so they will be coming back every day for 1000 consecutive days? - # -- but only 8 footprints for did_not_run event when switch to f. steril when consumables available but - # equipment not available in test_contraception (test_record_of_appt_footprint_for_switching_to_methods). - # What am I missing? ???? self._number_of_times_run < self.module.parameters['max_number_of_runs_of_hsi_if_consumable_not_available'] ): self.reschedule() diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index f4fcf508b4..debfdb3530 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -380,8 +380,6 @@ def get_and_store_newborn_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN EQUIPMENT ------------------------------------------------- - # TODO: As we now consider both consumables and equipment, using 'equipment' when meaning consumables is - # confusing self.item_codes_nb_consumables['iv_drug_equipment'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 2b5a5f4efa..654378c4bf 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4741,7 +4741,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # consumables used by surgeon, gloves and facemask + # equipment used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe @@ -5077,7 +5077,7 @@ def apply(self, person_id, squeeze_factor): get_item_code('Pethidine, 50 mg/ml, 2 ml ampoule'): 1, # administer antibiotic get_item_code("Ampicillin injection 500mg, PFR_each_CMST"): 1, - # consumables used by surgeon, gloves and facemask + # equipment used by surgeon, gloves and facemask get_item_code('Disposables gloves, powder free, 100 pieces per box'): 1, get_item_code('surgical face mask, disp., with metal nose piece_50_IDA'): 1, # request syringe diff --git a/tests/test_alri.py b/tests/test_alri.py index 0fba5fea8d..fb03312515 100644 --- a/tests/test_alri.py +++ b/tests/test_alri.py @@ -54,7 +54,7 @@ def _get_person_id(df, age_bounds: tuple = (0.0, np.inf)) -> int: ].index[0] -def get_sim(tmpdir, seed, cons_available, equip_available='all'): +def get_sim(tmpdir, seed, cons_available, equip_available): """Return simulation objection with Alri and other necessary modules registered.""" sim = Simulation( start_date=start_date, diff --git a/tests/test_contraception.py b/tests/test_contraception.py index b11402bfc2..388b834393 100644 --- a/tests/test_contraception.py +++ b/tests/test_contraception.py @@ -20,7 +20,6 @@ def run_sim(tmpdir, disable=False, healthsystem_disable_and_reject_all=False, consumables_available=True, - equipment_available=True, run=True, no_discontinuation=False, incr_prob_of_failure=False, @@ -55,14 +54,6 @@ def __check_dtypes(simulation): else: _cons_available = consumables_available - # Determine availability of equipment (True --> all available; False --> none available; other --> custom arg.) - if equipment_available is True: - _equip_available = 'all' - elif equipment_available is False: - _equip_available = 'none' - else: - _equip_available = equipment_available - resourcefilepath = Path(os.path.dirname(__file__)) / '../resources' start_date = Date(2010, 1, 1) @@ -89,7 +80,6 @@ def __check_dtypes(simulation): disable=disable, disable_and_reject_all=healthsystem_disable_and_reject_all, cons_availability=_cons_available, - equip_availability=_equip_available, ), # - modules for mechanistic representation of contraception -> pregnancy -> labour -> delivery etc. @@ -427,11 +417,8 @@ def test_record_of_appt_footprint_for_switching_to_methods(tmpdir, seed): """Check that the APPT_FOOTPRINTS recorded by the HealthSystem match the expectation: specifically, that the appointment depends on the nature of the switch and whether it is a reoccurrence.""" - def get_appt_footprints_when_did_not_or_did_run( - switch_from, switch_to, consumables_available, equipment_available - ) -> List[str]: - """Return a list of the APPT_FOOTPRINTS that are logged for one person for a particular switch, for HSI events - that 1) did not run, 2) did run.""" + def get_appt_footprints(switch_from, switch_to, consumables_available) -> List[str]: + """Return a list of the APPT_FOOTPRINTS that are logged for one person for a particular switch.""" person_id = 0 sim = run_sim(tmpdir, @@ -439,7 +426,6 @@ def get_appt_footprints_when_did_not_or_did_run( use_healthsystem=True, disable=False, consumables_available=consumables_available, - equipment_available=equipment_available, no_changes_in_contraception=True, no_discontinuation=True, equalised_risk_of_preg=0.0, @@ -450,7 +436,7 @@ def get_appt_footprints_when_did_not_or_did_run( # Set the person's initial sex, age and contraceptive method sim.population.props.at[person_id, 'sex'] = 'F' - sim.population.props.at[person_id, 'age_years'] = 31 + sim.population.props.at[person_id, 'age_years'] = 25 sim.population.props.at[person_id, 'co_contraception'] = switch_from # Schedule the initial HSI for the change @@ -464,193 +450,49 @@ def get_appt_footprints_when_did_not_or_did_run( sim.simulate(end_date=sim.start_date + pd.DateOffset(months=1)) hsi_run = parse_log_file(sim.log_filepath, level=logging.DEBUG)["tlo.methods.healthsystem"]["HSI_Event"] - return (hsi_run.loc[ - ~hsi_run.did_run + return hsi_run.loc[ + hsi_run.did_run & (hsi_run['Person_ID'] == person_id) & (hsi_run['TREATMENT_ID'] == 'Contraception_Routine'), 'Number_By_Appt_Type_Code' - ].to_list(), - hsi_run.loc[ - hsi_run.did_run - & (hsi_run['Person_ID'] == person_id) - & (hsi_run['TREATMENT_ID'] == 'Contraception_Routine'), 'Number_By_Appt_Type_Code' - ].to_list()) - - def assert_expected_conds_for_appt_footprints( - cond_did_not_run: list, cond_did_run: list, _switch_from: str, _switch_to: str - ) -> None: - """Returns true if footprints for did_not_run and did_run HSIs satisfy given conditions. - Possible Conditions: - ['appt_footprint_equal', 'appt_footprint']; - ['len_equal_0']; - ['len_greater_0_and_all_footprints_equal', 'appt_footprint']; - ['len_greater_1_and_first_footprint_equal_subsequent_blank', 'appt_footprint']; - ['none'] - """ - # print(f"\n{set_cons_avail=}") - # print(f"{set_equip_avail=}") - # print(f"{_switch_from=}") - # print(f"{_switch_to=}") - did_not_run_footprints, did_run_footprints = get_appt_footprints_when_did_not_or_did_run( - switch_from=_switch_from, switch_to=_switch_to, - consumables_available=set_cons_avail, equipment_available=set_equip_avail - ) - # Assert condition for did_not_run footprints - if cond_did_not_run[0] == 'len_equal_0': - assert 0 == len(did_not_run_footprints) - elif cond_did_not_run[0] == 'len_greater_0_and_all_footprints_equal': - assert 0 < len(did_not_run_footprints) and all([_x == cond_did_not_run[1] for _x in did_not_run_footprints]) - elif cond_did_not_run[0] == 'none': - assert [] == did_not_run_footprints - else: - warnings.warn(f'\nWarning: {cond_did_not_run=} does not exist.') - assert 0 - - # Assert condition for did_run footprints - if cond_did_run[0] == 'appt_footprint_equal': - assert [cond_did_run[1]] == did_run_footprints - elif cond_did_run[0] == 'len_greater_1_and_first_footprint_equal_subsequent_blank': - assert len(did_run_footprints) > 1 and did_run_footprints[0] == cond_did_run[1] and \ - (0 == len([_x for _i, _x in enumerate(did_run_footprints) if (_i != 0) and (_x != {})])) - elif cond_did_run[0] == 'none': - assert [] == did_run_footprints - else: - warnings.warn(f'\nWarning: {cond_did_run=} does not exist.') - assert 0 - - # 1) If both consumables and equipment available - set_cons_avail = True - set_equip_avail = True - # ... the HSI will only be run once, hence no footprint for did_not_run and exactly one footprint for did_run: - # - If switch to female_sterilization => 'MinorSurg' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'MinorSurg': 1}], - _switch_from='not_using', _switch_to='female_sterilization' - ) - # - If maintaining IUD => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'FamPlan': 1}], - _switch_from='IUD', _switch_to='IUD' - ) - # - If switching to anything new => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'FamPlan': 1}], - _switch_from='not_using', _switch_to='pill' - ) - # - If maintaining on implant => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'FamPlan': 1}], - _switch_from='implant', _switch_to='implant' - ) - # - If maintaining on pill => 'PharmDispensing' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'PharmDispensing': 1}], - _switch_from='pill', _switch_to='pill' - ) + ].to_list() - # 2) If consumables available, but equipment not available - set_cons_avail = True - set_equip_avail = False - # ... when a method with essential equipment (f. sterilization or IUD) requested, the HSI will never run hence no - # appt_footprint will be returned for did_run HSI, but it will be returned for did_not_run HSI multiple times: - # - If switch to female_sterilization => 'MinorSurg' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_greater_0_and_all_footprints_equal', {'MinorSurg': 1}], cond_did_run=['none'], - _switch_from='not_using', _switch_to='female_sterilization' - ) - # - If maintaining IUD => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_greater_0_and_all_footprints_equal', {'FamPlan': 1}], cond_did_run=['none'], - _switch_from='IUD', _switch_to='IUD' - ) - # ... otherwise the HSI will run and only once: + # 1) If consumables available, the HSI will only be run once: + # - If switch to female_sterilization => 'MinorSurg'" + assert [{'MinorSurg': 1}] == get_appt_footprints(switch_from='not_using', + switch_to='female_sterilization', + consumables_available=True) # - If switching to anything new => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'FamPlan': 1}], - _switch_from='not_using', _switch_to='pill' - ) + assert [{'FamPlan': 1}] == get_appt_footprints(switch_from='not_using', + switch_to='pill', + consumables_available=True) # - If maintaining on implant => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'FamPlan': 1}], - _switch_from='implant', _switch_to='implant' - ) + assert [{'FamPlan': 1}] == get_appt_footprints(switch_from='implant', + switch_to='implant', + consumables_available=True) # - If maintaining on pill => 'PharmDispensing' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_equal_0'], cond_did_run=['appt_footprint_equal', {'PharmDispensing': 1}], - _switch_from='pill', _switch_to='pill' - ) - - # 3) If consumables not available, but equipment available - set_cons_avail = False - set_equip_avail = True - # ... it does run and there should be multiple footprints, but only the first is non-blank: - # - If switch to female_sterilization => 'MinorSurg' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['none'], - cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'MinorSurg': 1}], - _switch_from='not_using', _switch_to='female_sterilization' - ) - # - If maintaining IUD => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['none'], - cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'FamPlan': 1}], - _switch_from='IUD', _switch_to='IUD' - ) - # - If switching to anything new => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['none'], - cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'FamPlan': 1}], - _switch_from='not_using', _switch_to='pill' - ) - # - If maintaining on implant => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['none'], - cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'FamPlan': 1}], - _switch_from='implant', _switch_to='implant' - ) - # - If maintaining on pill => 'PharmDispensing' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['none'], - cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'PharmDispensing': 1}], - _switch_from='pill', _switch_to='pill' - ) + assert [{'PharmDispensing': 1}] == get_appt_footprints(switch_from='pill', + switch_to='pill', + consumables_available=True) + + # 2) If consumables not available... there should be multiple footprints, but only the first is non-blank. + def is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank(x): + return ( + (len(x) > 1) + & (x[0] != {}) + & (0 == len([_x for _i, _x in enumerate(x) if (_i != 0) and (_x != {})])) + ) - # 4) If both consumables and equipment not available, ... - set_cons_avail = False - set_equip_avail = False - # ... when a method with essential equipment (f. sterilization or IUD) requested, the HSI will never run hence no - # appt_footprint will be returned for did_run HSI, but it will be returned for did_not_run HSI multiple times: - # - If switch to female_sterilization => 'MinorSurg' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_greater_0_and_all_footprints_equal', {'MinorSurg': 1}], cond_did_run=['none'], - _switch_from='not_using', _switch_to='female_sterilization' + assert is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank( + get_appt_footprints(switch_from='not_using', switch_to='female_sterilization', consumables_available=False) ) - # - If maintaining IUD => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['len_greater_0_and_all_footprints_equal', {'FamPlan': 1}], cond_did_run=['none'], - _switch_from='IUD', _switch_to='IUD' + assert is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank( + get_appt_footprints(switch_from='not_using', switch_to='pill', consumables_available=False) ) - # ... otherwise the HSI will run and there should be multiple footprints, but only the first is non-blank: - # TODO: this is the current logic, but it doesn't sound right, if it is never performed due to missing consumables - # it should never have the footprint equal, or do we expect that they will be coming again and again and again - # and again ... until they will get it in very far future? - - # - If switching to anything new => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['none'], - cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'FamPlan': 1}], - _switch_from='not_using', _switch_to='pill' + assert is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank( + get_appt_footprints(switch_from='implant', switch_to='implant', consumables_available=False) ) - # - If maintaining on implant => 'FamilyPlanning' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['none'], - cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'FamPlan': 1}], - _switch_from='implant', _switch_to='implant' - ) - # - If maintaining on pill => 'PharmDispensing' - assert_expected_conds_for_appt_footprints( - cond_did_not_run=['none'], - cond_did_run=['len_greater_1_and_first_footprint_equal_subsequent_blank', {'PharmDispensing': 1}], - _switch_from='pill', _switch_to='pill' + assert is_list_longer_than_length_of_one_and_with_first_element_nonblank_and_subsequent_blank( + get_appt_footprints(switch_from='pill', switch_to='pill', consumables_available=False) ) From cd2774e0fd1cdea503611ebb89a06ac0214686ad Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Thu, 9 May 2024 10:26:54 +0100 Subject: [PATCH 414/443] move `codes_to_items_list` to scripts/data-file-processing --- .../data_file_processing}/codes_to_items_list.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{tlo/analysis => scripts/data_file_processing}/codes_to_items_list.py (100%) diff --git a/src/tlo/analysis/codes_to_items_list.py b/src/scripts/data_file_processing/codes_to_items_list.py similarity index 100% rename from src/tlo/analysis/codes_to_items_list.py rename to src/scripts/data_file_processing/codes_to_items_list.py From 7a5d991a9c21b084135d68e627df045b4d39ca81 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Thu, 9 May 2024 10:28:07 +0100 Subject: [PATCH 415/443] roll back parsing script --- .../equipment/equipment_catalogue.py | 255 ------------------ 1 file changed, 255 deletions(-) delete mode 100644 src/scripts/healthsystem/equipment/equipment_catalogue.py diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py deleted file mode 100644 index 4dcd533903..0000000000 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ /dev/null @@ -1,255 +0,0 @@ -import argparse -import warnings -from pathlib import Path - -import pandas as pd - -from tlo.analysis.utils import extract_results - -# TODO: make these to be arguments of called fnc -# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# Declare whether to scale the counts to Malawi population size -# (True/False) -do_scaling = True -# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose any number) -# (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') -catalog_by_details = ['treatment_id', 'facility_level'] -# Declare which time period you want the equipment be grouped in the catalogue (choose only one) -# (periods: 'monthly', 'annual') -catalog_by_time = 'annual' -# Suffix for output file names -suffix_file_names = '__2y_2Kpop_4runs_1draw' -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -# TODO: Could I have use the bin_hsi_event_details from src/tlo/analysis/utils.py instead? If so, how? -def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: - """Returned pd.DataFrame gives the monthly counts of all the hsi event details logged (details as keys) - for each simulated month. - NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" - - def get_hsi_event_counts(_df): - """Get the counts of all the hsi event details logged.""" - - def unpack_dict_in_series(_raw: pd.Series): - # Create an empty DataFrame to store the data - df = pd.DataFrame() - - # Iterate through the dictionary items - for _, mydict in _raw.items(): - for date, inner_dict in mydict.items(): - # Convert the inner_dict to a list of dictionaries with 'date' - data = [{'date': date, 'event_details_key': inner_dict_key, 'count': inner_dict_set} for - inner_dict_key, inner_dict_set in inner_dict.items()] - # Create a DataFrame from the list with date & fac_level as indexes - temp_df = pd.DataFrame(data) - temp_df.set_index(['date', 'event_details_key'], inplace=True) - temp_df.columns = [None] - - # Concatenate the temporary DataFrame to the result DataFrame - df = pd.concat([df, temp_df]) - - df.columns = [None] - - return df - - return _df \ - .set_index('date') \ - .pipe(unpack_dict_in_series) \ - .stack() \ - .droplevel(level=2) - - return extract_results( - results_folder, - module='tlo.methods.healthsystem.summary', - key='hsi_event_counts', - custom_generate_series=get_hsi_event_counts, - do_scaling=do_scaling - ) - - -def get_hsi_event_keys_all_runs(results_folder: Path) -> pd.DataFrame: - """Returned pd.DataFrame gives the dictionaries of hsi_event_details for each draw and run. - NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" - - def get_hsi_event_keys(_df): - """Get the hsi_event_keys for one particular run.""" - return _df['hsi_event_key_to_event_details'] - - return extract_results( - results_folder, - module='tlo.methods.healthsystem.summary', - key='hsi_event_details', - custom_generate_series=get_hsi_event_keys - ) - - -def create_equipment_catalogues(results_folder: Path, output_folder: Path): - # %%% Verify inputs are as expected - assert isinstance(do_scaling, bool), "The input parameter 'do_scaling' must be a boolean (True or False)" - assert isinstance(catalog_by_details, list), "The input parameter 'catalog_by_details' must be a list" - event_details = \ - {'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint'} - for item in catalog_by_details: - assert isinstance(item, str) and item in event_details, \ - f"Each element in the input list 'catalog_by_details' must be a string and be one of the details:\n" \ - f"{event_details}" - assert catalog_by_time in {'monthly', 'annual'}, \ - "The input parameter 'catalog_by_time' must be one of the strings ('monthly' or 'annual')" - # --- - - # %%% Set output file names - # detailed CSV name - output_detailed_file_name = 'equipment_monthly_counts__all_event_details' + suffix_file_names + '.csv' - # requested details only CSV name - time_index = 'year' if catalog_by_time == 'annual' else 'date' - output_focused_file_name = \ - 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + \ - suffix_file_names + '.csv' - output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id' + suffix_file_names + '.csv' - # --- - - # %%% Load RF - # Equipment - equip_resource_items_pkgs_df = pd.read_csv( - 'resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv' - ) - - # %% Catalog equipment by all HSI event details - sim_equipment = get_monthly_hsi_event_counts(results_folder) - sim_equipment_df = pd.DataFrame(sim_equipment) - hsi_event_keys = get_hsi_event_keys_all_runs(results_folder) - - final_df = pd.DataFrame() - - def details_col_to_str(details_col): - return details_col.apply(lambda x: ', '.join(map(str, x))) - - def get_equip_item_name_from_item_code(equip_item_code: int) -> str: - """Helper function to provide the equip item name (a string) when provided with the equip_item_code (an int).""" - lookup_df = equip_resource_items_pkgs_df - return str(pd.unique(lookup_df.loc[lookup_df["Equip_Code"] == equip_item_code, "Equip_Item"])[0]) - - def get_equip_item_code_from_item_name(equip_item_name: str) -> int: - """Helper function to provide the equip item code (an int) when provided with the equip_item_name (a string)""" - lookup_df = equip_resource_items_pkgs_df - return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) - - def lists_of_equip_item_codes_to_strings_of_list_of_equip_item_names(list_of_equip_item_codes_col): - return list_of_equip_item_codes_col.apply( - lambda x: - str(sorted([get_equip_item_name_from_item_code(item_code) for item_code in x])) - ) - - def strings_of_list_to_lists_of_strings(strings_of_list_col): - lists_of_strings_col = strings_of_list_col.apply(lambda x: x.strip('][').split("'")) - return lists_of_strings_col.apply(lambda x: [s for s in x if (s != '' and s != ', ')]) - - for col in hsi_event_keys.columns: - df_col = sim_equipment_df[col].dropna() - decoded_keys = df_col.index.get_level_values(1).astype(str).map(hsi_event_keys.at[0, col]) - - # %%% Verify the keys in dictionary and dataframe for the run 'col' are same - # Check if all keys in hsi_event_keys_set are in the 'event_details_key' of df_col - hsi_event_keys_set = set(hsi_event_keys.at[0, col].keys()) - missing_keys_df =\ - [key for key in hsi_event_keys_set if key not in df_col.index.get_level_values('event_details_key')] - - # Check if all keys in the 'event_details_key' of df_col are in hsi_event_keys_set - missing_keys_dict =\ - [key for key in df_col.index.get_level_values('event_details_key') if key not in hsi_event_keys_set] - - # Warn if some keys are missing - if missing_keys_df: - warnings.warn(UserWarning(f"Keys missing in sim_equipment_df for the run {col}: {missing_keys_df}")) - - if missing_keys_dict: - warnings.warn(UserWarning(f"Keys missing in hsi_event_keys for the run {col}: {missing_keys_dict}")) - # %%% - - df_col = pd.concat([df_col, pd.DataFrame(decoded_keys.tolist(), index=df_col.index)], axis=1) - # Make values in 'appt_footprint', 'beddays_footprint', and 'equipment' columns to be string - df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) - df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) - df_col['equipment'] = lists_of_equip_item_codes_to_strings_of_list_of_equip_item_names(df_col['equipment']) - df_col = (df_col.droplevel(level=1) - .set_index(['module_name', 'event_name', 'treatment_id', 'facility_level', 'appt_footprint', - 'beddays_footprint', 'equipment'], append=True)) - final_df = pd.concat([final_df, df_col], axis=1) - - # Replace NaN with 0 - final_df.fillna(0, inplace=True) - final_df.sort_index(inplace=True) - # Save the detailed equipment catalogue - final_df.to_csv(output_folder / output_detailed_file_name) - print(f'{output_detailed_file_name} saved.') - # --- - - # %% Catalog equipment summary - equipment_summary = final_df.copy() - equipment_summary = equipment_summary.groupby(['module_name', 'event_name', 'treatment_id', 'equipment']).sum() - equipment_summary = \ - equipment_summary.reset_index().set_index(['module_name', 'event_name', 'treatment_id', 'equipment']) - # Save the summary equipment catalogue - equipment_summary.index.to_frame().to_csv(output_folder / output_summary_file_name, index=False) - print(f'{output_summary_file_name} saved.') - # --- - - # %% Catalog equipment by requested details - equipment_counts_by_time_and_requested_details = final_df.copy() - - # Sum counts for each equipment set with the same date, treatment id, and facility level - # (remaining indexes removed), keeping only non-empty 'equipment' indexes - to_be_grouped_by = ['date'] + catalog_by_details + ['equipment'] - equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( - to_be_grouped_by, - dropna=True - ).sum() - - if catalog_by_time == 'annual': - # Sum counts annually - equipment_counts_by_time_and_requested_details['year'] = \ - equipment_counts_by_time_and_requested_details.index.get_level_values('date').year - equipment_counts_by_time_and_requested_details.set_index('year', append=True, inplace=True) - equipment_counts_by_time_and_requested_details.index.droplevel('date') - to_be_grouped_by = ['year'] + catalog_by_details + ['equipment'] - equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( - to_be_grouped_by - ).sum() - - # Remove rows with no equipment used - equipment_counts_by_time_and_requested_details.drop("[]", level='equipment', axis=0, inplace=True) - equipment_counts_by_time_and_requested_details['equipment'] = \ - equipment_counts_by_time_and_requested_details.index.get_level_values('equipment') - equipment_counts_by_time_and_requested_details.index = \ - equipment_counts_by_time_and_requested_details.index.droplevel('equipment') - equipment_counts_by_time_and_requested_details['equipment'] = strings_of_list_to_lists_of_strings( - equipment_counts_by_time_and_requested_details['equipment'] - ) - exploded_df = equipment_counts_by_time_and_requested_details.explode('equipment') - # Add column with equip item code - exploded_df['equip_code'] = exploded_df['equipment'].apply(lambda x: get_equip_item_code_from_item_name(x)) - exploded_df = exploded_df.set_index(['equipment', 'equip_code'], append=True) - - # Sum values with the same multi-index - exploded_df = exploded_df.groupby(level=exploded_df.index.names).sum() - - # Save the equipment counts CSV - exploded_df.to_csv(output_folder / output_focused_file_name) - print(f'{output_focused_file_name} saved.') - # --- - - return 0 - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("results_folder", type=Path) - args = parser.parse_args() - - create_equipment_catalogues( - results_folder=args.results_folder, - output_folder=args.results_folder, - ) -# NB. Edit run configuration, the Parameters: -# "./outputs/sejjej5@ucl.ac.uk/equip_jobs/long_run_all_diseases-2023-09-04T233551Z" From 478155510ab70b82e967ebbb89f39d88ef6611a0 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Thu, 9 May 2024 21:14:36 +0100 Subject: [PATCH 416/443] squash - basic outline of Equipment class --- .../ResourceFile_Equipment.csv | 3 - .../ResourceFile_EquipmentCatalogue.csv | 3 + ...eFile_Equipment_Availability_Estimates.csv | 3 + src/tlo/analysis/utils.py | 1 + src/tlo/methods/equipment.py | 204 ++++++++++++ src/tlo/methods/healthsystem.py | 159 ++++++---- src/tlo/methods/hsi_event.py | 161 ++-------- tests/test_equipment.py | 295 ++++++++++++------ 8 files changed, 550 insertions(+), 279 deletions(-) delete mode 100644 resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv create mode 100644 resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv create mode 100644 resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment_Availability_Estimates.csv create mode 100644 src/tlo/methods/equipment.py diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv deleted file mode 100644 index 5700c76b27..0000000000 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:20c07f76ec100a3c1a221c4c8ecc4bd6e37379f395b08ae67491501d04f04d6e -size 45343 diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv new file mode 100644 index 0000000000..2f3ca59328 --- /dev/null +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d21fb0f546325fd264b0598efd7afbea15ad48564ed28cb9df970509ce1d405 +size 33196 diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment_Availability_Estimates.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment_Availability_Estimates.csv new file mode 100644 index 0000000000..829b95d1f9 --- /dev/null +++ b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment_Availability_Estimates.csv @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7c0364af516509b47f161278a061817d22bcd06685f469c09be089454c02a22 +size 582777 diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index 3aeff2bb11..36cee558d4 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -1125,6 +1125,7 @@ def get_parameters_for_status_quo() -> Dict: "mode_appt_constraints": 1, "cons_availability": "default", "beds_availability": "default", + "equip_availability": "all", # <--- NB. Existing calibration is assuming all equipment is available }, } diff --git a/src/tlo/methods/equipment.py b/src/tlo/methods/equipment.py new file mode 100644 index 0000000000..2bb1f15267 --- /dev/null +++ b/src/tlo/methods/equipment.py @@ -0,0 +1,204 @@ +import warnings +from collections import defaultdict +from typing import Dict, Set, Iterable, Union, Counter, Optional, Any + +import numpy as np +import pandas as pd + +from tlo import logging + +logger_summary = logging.getLogger("tlo.methods.healthsystem.summary") + + +class Equipment: + """This is the Equipment Class. It maintains a current record of the availability of equipment in the + HealthSystem. It is expected that this is instantiated by the `HealthSystem` module. + + :param: 'catalogue': The database of all recognised item_codes. + + :param: `data_availability`: Specifies the probability with which each equipment (identified by an `item_code`) is + available at a facility level. Note that information is not necessarily provided for every item in the `catalogue`. + + :param: `rng`: The Random Number Generator object to use for random numbers. + + :param: `availability`: Determines the mode availability of the equipment. If 'default' then use the availability + specified in the ResourceFile; if 'none', then let no equipment be ever be available; if 'all', then all + equipment is always available. + + If an item_code is requested that is not recognised (not included in `data`), a `UserWarning` is issued, and the + result returned is on the basis of the average availability of other consumables in that facility in that month. + """ + + def __init__( + self, + catalogue: pd.DataFrame, + data_availability: pd.DataFrame, + rng: np.random, + master_facilities_list: pd.DataFrame, + availability: Optional[str] = "default", + ) -> None: + # Store arguments + self.catalogue = catalogue + self.rng = rng + self.data_availability = data_availability + self.master_facilities_list = master_facilities_list + + # Create internal storage structures + self._items_available: Dict = dict() # Will be the internal store of which items are available at each + # facility_id. This is of the form {facility_id: {items_available}}. + + self._record_of_equipment_used = defaultdict(Counter) # Will be the internal store of which items have been + # used at each facility_id. This is of the form + # {facility_id: {item_code: count}}. + + # Set up the internal stores of equipment items that are available, ready for calls. + self._set_equipment_items_available(availability=availability) + + # Set up internal lookup for item_descriptor -> item_code + self.item_code_lookup = self.catalogue.set_index('Description')['Item_Code'].to_dict() + + def on_simulation_end(self): + """Things to do when the simulation end: + * Log (to the summary logger) the equipment that has been used. + """ + self.write_to_log() + + def update_availability(self, availability) -> None: + """Update the availability of equipment. This is expected to be called midway through the simulation if + the assumption of the equipment availability needs to change.""" + self._set_equipment_items_available(availability=availability) + + def _set_equipment_items_available(self, availability: str): + """Update internal store of which items of equipment are available. This is called at the beginning of the + simulation and whenever an update in `availability` is needed.""" + + # For any facility_id in the data + all_fac_ids = self.master_facilities_list['Facility_ID'].unique() + + # All equipment items in the catalogue + all_eq_items = self.catalogue["Item_Code"].unique() + + # Create full dataset, where we force that there is probability of availability for every item_code at every + # observed facility + df = pd.Series( + index=pd.MultiIndex.from_product( + [all_fac_ids, all_eq_items], names=["Facility_ID", "Item_Code"] + ), + data=float("nan"), + ).combine_first( + self.data_availability.set_index(["Facility_ID", "Item_Code"])[ + "Pr_Available" + ] + ) + + # Merge in original dataset and use the mean in that facility_id to impute availability of missing item_code + df = df.groupby("Facility_ID").transform(lambda x: x.fillna(x.mean())) + # ... and also impute availability for any facility_ids for which no data, based on all other facilities + df = df.groupby("Item_Code").transform(lambda x: x.fillna(x.mean())) + + # Check no missing values + assert not df.isnull().any() + + # Over-write these data if `availability` argument specifies that `none` or `all` items should be available + if availability == "default": + pass + elif availability == "all": + df = (df + 1).clip(upper=1.0) + elif availability == "none": + df = df.mul(0.0) + else: + raise KeyError(f"Unknown equipment availability specified: {availability}") + + # Sample these probability to find which items are actually available + is_available = df > self.rng.random(size=len(df)) + + # Organise into dict of set, of the form: {facility_id: {items_available}} for known facility_ids + # (N.B. Has to be done this way around in order to guarantee that we have each known facility_id in the keys + # even if there are no item available.) + self._items_available: Dict = is_available.groupby("Facility_ID").agg( + lambda x: set(x[x].index.get_level_values("Item_Code")) + ).to_dict() + + def _parse_items(self, items: Union[int, str, Iterable[int | str]]) -> Set[int]: + """Parse equipment items specified as an item_code (integer), an item descriptor (string), or an iterable of + either, and return as a set of item_code (integers).""" + + def first_element_in_iterable(it: Iterable[Any]) -> Any | None: + for el in it: + return el + + if isinstance(items, str): + # Single descriptor + return set([self.item_code_lookup(items)]) + elif isinstance(items, int): + # Single item_code provided + return set([items]) + elif isinstance(items, Iterable) and len(items) == 0: + # Iterable of length 0 + return set() + elif isinstance(items, Iterable) and isinstance(first_element_in_iterable(items), str): + # Iterable of descriptors + return set(map(self.item_code_lookup, items)) + elif isinstance(items, Iterable) and isinstance(first_element_in_iterable(items), int): + # Iterable of item_cods + return set(items) + + else: + raise ValueError(f'Item_Code format not recognised: {items=}') + + if isinstance(items, int): + item_codes = set([items]) # If single int provided, place it into a list + elif isinstance(items, list): + item_codes = set(items) + + + def is_all_items_available( + self, item_codes: Union[int, str, Iterable[int | str]], facility_id: int + ) -> bool: + """Determine if all equipments are available at the given facility_id (or from the default if the faciluty_id + is not recognised). Returns True only if all items are available at the facility_id, otherwise returns False.""" + return self._parse_items(item_codes).issubset(self._items_available[facility_id]) + + + def record_use_of_equipment( + self, item_codes: Union[int, str, Iterable[int | str]], facility_id: int + ) -> None: + """Update internal record of the usage of items at equipment at the specified facility_id.""" + self._record_of_equipment_used[facility_id].update(self._parse_items(item_codes)) + + def write_to_log(self) -> None: + """Write to the log: + * Summary of the equipment that was _ever_ used at each facility_level + * For each facility_id, a set of the equipment items ever used. + """ + + mfl = self.master_facilities_list + + def set_of_keys_or_empty_set(x: Union[set, dict]): + if isinstance(x, set): + return x + elif isinstance(x, dict): + return set(x.keys()) + else: + return None + + set_of_equipment_ever_used_at_each_facility_id = pd.Series({ + fac_id: set_of_keys_or_empty_set(self._record_of_equipment_used.get(fac_id, set())) + for fac_id in mfl['Facility_ID'] + }, name='EquipmentEverUsed').astype(str) + + output = mfl.merge( + set_of_equipment_ever_used_at_each_facility_id, + left_on='Facility_ID', + right_index=True, + how='left', + ).drop(columns=['Facility_ID', 'Facility_Name']) + + # Log multi-row data-frame + for _, row in output.iterrows(): + logger_summary.info( + key='EquipmentEverUsed_ByFacilityID', + description='For each facility_id (the set of facilities of the same level in a district), the set' + 'equipment items that are ever used.', + data=row.to_dict(), + ) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 62e9f2fe01..a0a3e6b96f 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -27,6 +27,7 @@ get_item_codes_from_package_name, ) from tlo.methods.dxmanager import DxManager +from tlo.methods.equipment import Equipment from tlo.methods.hsi_event import ( LABEL_FOR_MERGED_FACILITY_LEVELS_1B_AND_2, FacilityInfo, @@ -193,13 +194,17 @@ class HealthSystem(Module): "Availability of beds. If 'default' then use the availability specified in the ResourceFile; if " "'none', then let no beds be ever be available; if 'all', then all beds are always available. NB. This " "parameter is over-ridden if an argument is provided to the module initialiser."), - 'Equipment': Parameter( - Types.DATA_FRAME, "Data on equipment items, packages, and availability probabilities by facility level."), + 'EquipmentCatalogue': Parameter( + Types.DATA_FRAME, "Data on equipment items and packages."), + 'equipment_availability_estimates': Parameter( + Types.DATA_FRAME, "Data on the availability of equipment items and packages." + ), 'equip_availability': Parameter( Types.STRING, - "Availability of equipment. If 'default' then use the availability specified in the ResourceFile;" - " if 'none', then let no equipment ever be available; if 'all', then all equipment is always available. NB." - " This parameter is over-ridden if an argument is provided to the module initialiser."), + "What to assume about the availability of equipment. If 'default' then use the availability specified in " + "the ResourceFile; if 'none', then let no equipment ever be available; if 'all', then all equipment is " + "always available. NB. This parameter is over-ridden if an argument is provided to the module initialiser." + ), # Service Availability 'Service_Availability': Parameter( @@ -542,8 +547,14 @@ def read_parameters(self, data_folder): path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Bed_Capacity.csv') # Read in ResourceFile_Equipment - self.parameters['Equipment'] = pd.read_csv( - path_to_resourcefiles_for_healthsystem / 'infrastructure_and_equipment' / 'ResourceFile_Equipment.csv') + self.parameters['EquipmentCatalogue'] = pd.read_csv( + path_to_resourcefiles_for_healthsystem + / 'infrastructure_and_equipment' + / 'ResourceFile_EquipmentCatalogue.csv') + self.parameters['equipment_availability_estimates'] = pd.read_csv( + path_to_resourcefiles_for_healthsystem + / 'infrastructure_and_equipment' + / 'ResourceFile_Equipment_Availability_Estimates.csv') # Data on the priority of each Treatment_ID that should be adopted in the queueing system according to different # priority policies. Load all policies at this stage, and decide later which one to adopt. @@ -600,6 +611,7 @@ def pre_initialise_population(self): self.rng_for_hsi_queue = np.random.RandomState(self.rng.randint(2 ** 31 - 1)) self.rng_for_dx = np.random.RandomState(self.rng.randint(2 ** 31 - 1)) rng_for_consumables = np.random.RandomState(self.rng.randint(2 ** 31 - 1)) + rng_for_equipment = np.random.RandomState(self.rng.randint(2 ** 31 - 1)) # Determine mode_appt_constraints self.mode_appt_constraints = self.get_mode_appt_constraints() @@ -625,8 +637,13 @@ def pre_initialise_population(self): ) # Determine equip_availability - # todo - create Equipment class here - self.equip_availability = self.get_equip_availability() + self.equipment = Equipment( + catalogue=self.parameters['EquipmentCatalogue'], + data_availability=self.parameters['equipment_availability_estimates'], + rng=rng_for_equipment, + master_facilities_list=self.parameters['Master_Facilities_List'], + availability=self.get_equip_availability(), + ) self.tclose_overwrite = self.parameters['tclose_overwrite'] self.tclose_days_offset_overwrite = self.parameters['tclose_days_offset_overwrite'] @@ -713,6 +730,8 @@ def on_simulation_end(self): """Put out to the log the information from the tracker of the last day of the simulation""" self.bed_days.on_simulation_end() self.consumables.on_simulation_end() + self.equipment.on_simulation_end() + if self._hsi_event_count_log_period == "simulation": self._write_hsi_event_counts_to_log_and_reset() self._write_never_ran_hsi_event_counts_to_log_and_reset() @@ -2169,9 +2188,19 @@ def process_events_mode_0_and_1(self, hold_over: List[HSIEventQueueItem]) -> Non # Run the list of population-level HSI events self.module.run_population_level_events(list_of_population_hsi_event_tuples_due_today) - # Run the list of individual-level events + # For each individual level event, check whether the equipment it has already declared is available. If it + # is not, then call the HSI's never_run function, and do not take it forward for running; if it is then + # add it to the list of events to run. + list_of_individual_hsi_event_tuples_due_today_that_have_essential_equipment = list() + for item in list_of_individual_hsi_event_tuples_due_today: + if not item.hsi_event.is_all_declared_equipment_available: + self.module.call_and_record_never_ran_hsi_event(hsi_event=item.hsi_event, priority=item.priority) + else: + list_of_individual_hsi_event_tuples_due_today_that_have_essential_equipment.append(item) + + # Try to run the list of individual-level events that have their essential equipment _to_be_held_over = self.module.run_individual_level_events_in_mode_0_or_1( - list_of_individual_hsi_event_tuples_due_today, + list_of_individual_hsi_event_tuples_due_today_that_have_essential_equipment, ) hold_over.extend(_to_be_held_over) @@ -2198,7 +2227,6 @@ def process_events_mode_2(self, hold_over: List[HSIEventQueueItem]) -> None: list_of_population_hsi_event_tuples_due_today = list() list_of_events_not_due_today = list() - # todo - check if essential equipment available and do not run if not - in any mode # Traverse the queue and run events due today until have capabilities still available while len(self.module.HSI_EVENT_QUEUE) > 0: @@ -2308,58 +2336,66 @@ def process_events_mode_2(self, hold_over: List[HSIEventQueueItem]) -> None: assert event.facility_info is not None, \ f"Cannot run HSI {event.TREATMENT_ID} without facility_info being defined." - # Expected appt footprint before running event - _appt_footprint_before_running = event.EXPECTED_APPT_FOOTPRINT - # Run event & get actual footprint - actual_appt_footprint = event.run(squeeze_factor=squeeze_factor) + # Check if equipment declared is available. If not, call `never_ran` and do not run the + # event. + if not event.is_all_declared_equipment_available: + self.module.call_and_record_never_ran_hsi_event( + hsi_event=event, + priority=next_event_tuple.priority + ) + else: + # Expected appt footprint before running event + _appt_footprint_before_running = event.EXPECTED_APPT_FOOTPRINT + # Run event & get actual footprint + actual_appt_footprint = event.run(squeeze_factor=squeeze_factor) - # Check if the HSI event returned updated_appt_footprint, and if so adjust original_call - if actual_appt_footprint is not None: + # Check if the HSI event returned updated_appt_footprint, and if so adjust original_call + if actual_appt_footprint is not None: - # check its formatting: - assert self.module.appt_footprint_is_valid(actual_appt_footprint) + # check its formatting: + assert self.module.appt_footprint_is_valid(actual_appt_footprint) - # Update call that will be used to compute capabilities used - updated_call = self.module.get_appt_footprint_as_time_request( - facility_info=event.facility_info, - appt_footprint=actual_appt_footprint + # Update call that will be used to compute capabilities used + updated_call = self.module.get_appt_footprint_as_time_request( + facility_info=event.facility_info, + appt_footprint=actual_appt_footprint + ) + else: + actual_appt_footprint = _appt_footprint_before_running + updated_call = original_call + + # Recalculate call on officers based on squeeze factor. + for k in updated_call.keys(): + updated_call[k] = updated_call[k]/(squeeze_factor + 1.) + + # Subtract this from capabilities used so-far today + capabilities_monitor.subtract(updated_call) + + # If any of the officers have run out of time by performing this hsi, + # remove them from list of available officers. + for officer, call in updated_call.items(): + if capabilities_monitor[officer] <= 0: + if officer in set_capabilities_still_available: + set_capabilities_still_available.remove(officer) + else: + logger.warning( + key="message", + data=(f"{event.TREATMENT_ID} actual_footprint requires different" + f"officers than expected_footprint.") + ) + + # Update today's footprint based on actual call and squeeze factor + self.module.running_total_footprint -= original_call + self.module.running_total_footprint += updated_call + + # Write to the log + self.module.record_hsi_event( + hsi_event=event, + actual_appt_footprint=actual_appt_footprint, + squeeze_factor=squeeze_factor, + did_run=True, + priority=_priority ) - else: - actual_appt_footprint = _appt_footprint_before_running - updated_call = original_call - - # Recalculate call on officers based on squeeze factor. - for k in updated_call.keys(): - updated_call[k] = updated_call[k]/(squeeze_factor + 1.) - - # Subtract this from capabilities used so-far today - capabilities_monitor.subtract(updated_call) - - # If any of the officers have run out of time by performing this hsi, - # remove them from list of available officers. - for officer, call in updated_call.items(): - if capabilities_monitor[officer] <= 0: - if officer in set_capabilities_still_available: - set_capabilities_still_available.remove(officer) - else: - logger.warning( - key="message", - data=(f"{event.TREATMENT_ID} actual_footprint requires different" - f"officers than expected_footprint.") - ) - - # Update today's footprint based on actual call and squeeze factor - self.module.running_total_footprint -= original_call - self.module.running_total_footprint += updated_call - - # Write to the log - self.module.record_hsi_event( - hsi_event=event, - actual_appt_footprint=actual_appt_footprint, - squeeze_factor=squeeze_factor, - did_run=True, - priority=_priority - ) # Don't have any capabilities at all left for today, no # point in going through the queue to check what's left to do today. @@ -2714,8 +2750,7 @@ def apply(self, population): self.module.bed_days.availability = self._parameters['beds_availability'] if 'equip_availability' in self._parameters: - # todo - is this being directed to right place? - self.module.equip_availability = self._parameters['equip_availability'] + self.module.equipment.update_availability(self._parameters['equip_availability']) class DynamicRescalingHRCapabilities(RegularEvent, PopulationScopeEventMixin): diff --git a/src/tlo/methods/hsi_event.py b/src/tlo/methods/hsi_event.py index b2c20ea7a9..8598cb1294 100644 --- a/src/tlo/methods/hsi_event.py +++ b/src/tlo/methods/hsi_event.py @@ -1,10 +1,9 @@ from __future__ import annotations from collections import Counter -from typing import TYPE_CHECKING, Dict, Literal, NamedTuple, Optional, Set, Tuple, Union +from typing import TYPE_CHECKING, Dict, Literal, NamedTuple, Optional, Set, Tuple, Union, Iterable import numpy as np -import pandas as pd from tlo import Date, logging from tlo.events import Event @@ -79,7 +78,7 @@ class HSI_Event: """ module: Module - target: int # Will be overwritten by the mixin on derived classes + target: int # Will be overwritten by the mixin on derived classes TREATMENT_ID: str ACCEPTED_FACILITY_LEVEL: str @@ -87,6 +86,12 @@ class HSI_Event: # which have been loaded. BEDDAYS_FOOTPRINT: Dict[str, Union[float, int]] + _EQUIPMENT: Set[int] = set() # The set of equipment that is used in the HSI. If any items in this set are not + # available at the point when the HSI will be run, then the HSI is not run, and the + # `never_ran` method is called instead. This is a declaration of resource needs, but + # is private because users are expected to use `add_equipment` to declare equipment + # needs. + _received_info_about_bed_days: Dict[str, Union[float, int]] = None expected_time_requests: Counter = {} facility_info: FacilityInfo = None @@ -109,7 +114,7 @@ def __init__(self, module, *args, **kwargs): self.expected_time_requests = {} self.facility_info = None self.ESSENTIAL_EQUIPMENT = None - self.EQUIPMENT = set() + self.EQUIPMENT = set() # todo should this be private, and should we add setter methods self.TREATMENT_ID = "" self.ACCEPTED_FACILITY_LEVEL = None @@ -266,125 +271,25 @@ def make_appt_footprint(self, dict_of_appts) -> Counter: "values" ) - def get_equip_item_code_from_item_name(self, equip_item_name: str) -> int: - """Helper function to provide the equip_item_code (an int) when provided with the equip_item_name of the item""" - lookup_df = self.sim.modules['HealthSystem'].parameters['Equipment'] - return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) - - def get_equip_item_codes_from_pkg_name(self, equip_pkg_name: str) -> Set[int]: - """Helper function to provide the equip_item_codes (a set of ints) when provided with the equip_pkg_name of the - equipment package""" - lookup_df = self.sim.modules['HealthSystem'].parameters['Equipment'] - return set(lookup_df.loc[lookup_df["Equip_Pkg"] == equip_pkg_name, "Equip_Code"]) - - def ignore_unknown_equip_names(self, set_of_names: Set[str], type_in_set: str) -> Set[str]: - """Helper function to check if the equipment item or pkg names (depending on type_in_set: 'item' or 'pkg') from - the provided set are in the RF_Equipment. If they are not, they are added to a set to be warned about at the end - of the simulation. - - Only known (item or pkg) names are returned.""" - if set_of_names in [set(), None, {''}]: - return set() - - def add_unknown_names_to_dict(unknown_names_to_add: Set[str], dict_to_be_added_to: Dict) -> Dict: - if self.__class__.__name__ not in dict_to_be_added_to.keys(): - dict_to_be_added_to.update( - {self.__class__.__name__: unknown_names_to_add} - ) - else: - dict_to_be_added_to[self.__class__.__name__].update( - unknown_names_to_add - ) - return dict_to_be_added_to - - lookup_df = self.sim.modules['HealthSystem'].parameters['Equipment'] - if type_in_set == "item": - unknown_names = set_of_names.difference(set(lookup_df["Equip_Item"])) - if unknown_names: - self.sim.modules['HealthSystem']._equip_items_missing_in_RF = \ - add_unknown_names_to_dict( - unknown_names, self.sim.modules['HealthSystem']._equip_items_missing_in_RF - ) - - elif type_in_set == "pkg": - unknown_names = set_of_names.difference(set(lookup_df["Equip_Pkg"])) - if unknown_names: - self.sim.modules['HealthSystem']._equip_pkgs_missing_in_RF = \ - add_unknown_names_to_dict( - unknown_names, self.sim.modules['HealthSystem']._equip_pkgs_missing_in_RF - ) - # TODO: What happens if all equip in set_of_names has unknown name? - return set_of_names.difference(unknown_names) - - def set_equipment_essential_to_run_event(self, set_of_equip: Set[str]) -> None: - """Helper function to set essential equipment. - - Should be passed a set of equipment items names (strings) or an empty set. - """ - # Set EQUIPMENT if the given set_of_equip in correct format, ie a set of strings or an empty set - if not isinstance(set_of_equip, set) or any(not isinstance(item, str) for item in set_of_equip): - raise ValueError( - "Argument to set_equipment_essential_to_run_event should be an empty set or a set of strings of " - "equipment item names from ResourceFile_Equipment.csv." - ) - - set_of_equip = self.ignore_unknown_equip_names(set_of_equip, "item") - if set_of_equip: - equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) - self.ESSENTIAL_EQUIPMENT = equip_codes - else: - self.ESSENTIAL_EQUIPMENT = set() - - # todo add function to set essential equipment + def add_equipment(self, item_codes: Union[int, str, Iterable[int | str]]): + """Declare that piece(s) of equipment are used in this HSI_Event.""" + self._EQUIPMENT.update(self.healthcare_system.equipment._parse_items(item_codes)) - def add_equipment(self, set_of_equip: Set[str]) -> None: - """Helper function to update equipment. - - Should be passed a set of equipment item names (strings). - """ - # Update EQUIPMENT if the given set_of_equip in correct format, ie a non-empty set of strings - if ( - (not isinstance(set_of_equip, set)) - or - any(not isinstance(item, str) for item in set_of_equip) - or - (set_of_equip in [set(), None, {''}]) - ): - raise ValueError( - "Argument to add_equipment should be a non-empty set of strings of " - "equipment item names from ResourceFile_Equipment.csv." - ) - # from the set of equip item names create a set of equip item codes, ignore unknown equip names - # (ie not included in RF_Equipment) - set_of_equip = self.ignore_unknown_equip_names(set_of_equip, "item") - if set_of_equip: - equip_codes = set(self.get_equip_item_code_from_item_name(item_name) for item_name in set_of_equip) - self.EQUIPMENT.update(equip_codes) - - def add_equipment_from_pkg(self, set_of_pkgs: Set[str]) -> None: - """Helper function to update equipment with equipment from pkg(s). + @property + def is_all_declared_equipment_available(self) -> bool: + """Returns True if all the declared items of equipment are available. This is called before the HSI is run + so is looking only at those items that are declared when this instance was created.""" + return self.healthcare_system.equipment.is_all_items_available( + item_codes=self._EQUIPMENT, + facility_id=self.facility_info.id, + ) - Should be passed a set of equipment pkgs names (strings). - """ - # Update EQUIPMENT if the given set_of_pkgs in correct format, ie a non-empty set of strings - if not isinstance(set_of_pkgs, set) or any(not isinstance(item, str) for item in set_of_pkgs) or \ - (set_of_pkgs in [set(), None, {''}]): - raise ValueError( - "Argument to add_equipment_from_pkg should be a non-empty set of strings of " - "equipment pkg names from ResourceFile_Equipment.csv." - ) - # update EQUIPMENT with eqip item codes from equip pkgs with provided names, ignore unknown equip names - # (ie not included in RF_Equipment) - set_of_pkgs = self.ignore_unknown_equip_names(set_of_pkgs, "pkg") - if set_of_pkgs: - for pkg_name in set_of_pkgs: - self.EQUIPMENT.update(self.get_equip_item_codes_from_pkg_name(pkg_name)) - - def get_essential_equip_availability(self, set_of_pkgs: Set[str]) -> bool: - # TODO: Or, should it be called set_essential_equip_and_get_availability to be more transparent about what the - # fnc does? - self.set_equipment_essential_to_run_event(set_of_pkgs) - return self.sim.modules['HealthSystem'].get_essential_equip_availability(self.ESSENTIAL_EQUIPMENT) + def is_equipment_available(self, item_codes: Union[int, Iterable[int]]) -> bool: + """Check whether all the equipment item_codes are available.""" + return self.healthcare_system.equipment.is_all_items_available( + item_codes=item_codes, + facility_id=self.facility_info.id, + ) def initialise(self) -> None: """Initialise the HSI: @@ -422,11 +327,6 @@ def initialise(self) -> None: # Do checks self._check_if_appt_footprint_can_run() - # Set essential equip to empty set if not exists and warn about missing settings - if self.ESSENTIAL_EQUIPMENT is None: - self.set_equipment_essential_to_run_event({''}) - self.sim.modules['HealthSystem']._hsi_event_names_missing_ess_equip.update({self.__class__.__name__}) - def _check_if_appt_footprint_can_run(self) -> bool: """Check that event (if individual level) is able to run with this configuration of officers (i.e. check that this does not demand officers that are _never_ available), and issue warning if not. @@ -499,7 +399,14 @@ def as_namedtuple( beddays_footprint=tuple( sorted((k, v) for k, v in self.BEDDAYS_FOOTPRINT.items() if v > 0) ), - equipment=(tuple(sorted(self.EQUIPMENT))), + equipment=(tuple(sorted(self.EQUIPMENT))), # todo check if this is defined in HSIEventDetails and whether we want this here + ) + + def post_apply_hook(self): + """Record the equipment that has been added before and during the course of the HSI Event.""" + self.healthcare_system.equipment.record_use_of_equipment( + item_codes=self._EQUIPMENT, + facility_id=self.facility_info.id ) diff --git a/tests/test_equipment.py b/tests/test_equipment.py index 3d15294c9a..96faf660f8 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -1,47 +1,158 @@ """This file contains all the tests to do with Equipment use logging and availability checks.""" import os +import time from pathlib import Path from typing import Union, Dict, Iterable +import pytest + +import numpy as np import pandas as pd -from tlo import Simulation, Module, Date +from tlo import Simulation, Module, Date, logging from tlo.analysis.utils import parse_log_file from tlo.events import IndividualScopeEventMixin from tlo.methods import Metadata, demography, healthsystem +from tlo.methods.equipment import Equipment from tlo.methods.hsi_event import HSI_Event -resourcefilepath = Path(os.path.dirname(__file__)) / '../resources' +resourcefilepath = Path(os.path.dirname(__file__)) / "../resources" -equipment_item_code_that_is_available = [0, 1, ] -equipment_item_code_that_is_not_available = [2, 3, ] +def test_core_functionality_of_equipment_class(seed): + """Test that the core functionality of the equipment class works on toy data.""" + + # Create toy data + catalogue = pd.DataFrame( + [ + {"Description": "ItemZero", "Item_Code": 0}, + {"Description": "ItemOne", "Item_Code": 1}, + {"Description": "ItemTwo", "Item_Code": 2}, + {"Description": "ItemThree", "Item_Code": 3}, + ] + ) + data_availability = pd.DataFrame( + # item 0 is not available anywhere; item 1 is available everywhere; item 2 is available only at facility_id=1 + # No data for fac_id=2 + [ + {"Item_Code": 0, "Facility_ID": 0, "Pr_Available": 0.0}, + {"Item_Code": 0, "Facility_ID": 1, "Pr_Available": 0.0}, + {"Item_Code": 1, "Facility_ID": 0, "Pr_Available": 1.0}, + {"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}, + ] + ) + + mfl = pd.DataFrame( + [ + {'District': 'D0', 'Facility_Level': '1a', 'Region': 'R0', 'Facility_ID': 0, 'Facility_Name': 'Fac0'}, + {'District': 'D0', 'Facility_Level': '1b', 'Region': 'R0', 'Facility_ID': 1, 'Facility_Name': 'Fac1'}, + {'District': 'D0', 'Facility_Level': '2', 'Region': 'R0', 'Facility_ID': 2, 'Facility_Name': 'Fac2'}, + ] + ) + + # Create instance of the Equipment class with these toy data and check availability of equipment... + # -- when using `default` behaviour: + eq_default = Equipment( + catalogue=catalogue, + data_availability=data_availability, + rng=np.random.RandomState(np.random.MT19937(np.random.SeedSequence(seed))), + master_facilities_list=mfl, + availability="default", + ) + # - using single integer for one item_code (item should be available) + assert eq_default.is_all_items_available(item_codes=1, facility_id=1) + # - using list of integers for item_codes (items should be available) + assert eq_default.is_all_items_available(item_codes=[1, 2], facility_id=1) + # - calling an item for which data on availability is not provided (should not raise error) + eq_default.is_all_items_available(item_codes=3, facility_id=1) + # - calling an item at a facility that for which data is not provided (should give average behaviour for other facilities) + assert not eq_default.is_all_items_available(item_codes=0, facility_id=2) + assert eq_default.is_all_items_available(item_codes=1, facility_id=2) + # - calling an item for which no data at a facility with no data (should not error) + eq_default.is_all_items_available(item_codes=3, facility_id=2) + # - calling when all items available (should be true) + assert eq_default.is_all_items_available(item_codes=[1, 2], facility_id=1) + # - calling when no items available (should be false) + assert not eq_default.is_all_items_available(item_codes=[0, 2], facility_id=0) + # - calling when some items available (should be false) + assert not eq_default.is_all_items_available(item_codes=[1, 2], facility_id=0) + + # -- when using `none` availability behaviour: everything should not be available! + eq_none = Equipment( + catalogue=catalogue, + data_availability=data_availability, + rng=np.random.RandomState(np.random.MT19937(np.random.SeedSequence(seed))), + availability="none", + master_facilities_list=mfl, + ) + # - calling when all items available (should be false because using 'none' behaviour) + assert not eq_none.is_all_items_available(item_codes=[1, 2], facility_id=1) + # - calling when no items available (should be false) + assert not eq_none.is_all_items_available(item_codes=[0, 2], facility_id=0) + # - calling when some items available (should be false) + assert not eq_none.is_all_items_available(item_codes=[1, 2], facility_id=0) + + # -- when using `all` availability behaviour: everything should not be available! + eq_all = Equipment( + catalogue=catalogue, + data_availability=data_availability, + rng=np.random.RandomState(np.random.MT19937(np.random.SeedSequence(seed))), + availability="all", + master_facilities_list=mfl, + ) + # - calling when all items available (should be true) + assert eq_all.is_all_items_available(item_codes=[1, 2], facility_id=1) + # - calling when no items available (should be true because using 'all' behaviour) + assert eq_all.is_all_items_available(item_codes=[0, 2], facility_id=0) + # - calling when some items available (should be true because using 'all' behaviour) + assert eq_all.is_all_items_available(item_codes=[1, 2], facility_id=0) + + # Check recording use of equipment + # - Add records, using calls with integers and list to different facility_id + eq_default.record_use_of_equipment(item_codes=1, facility_id=0) + eq_default.record_use_of_equipment(item_codes=[0, 1], facility_id=0) + eq_default.record_use_of_equipment(item_codes=[0, 1], facility_id=1) + # - Check that internal record is as expected + assert dict(eq_default._record_of_equipment_used) == {0: {0: 1, 1: 2}, 1: {0: 1, 1: 1}} -def run_simulation_return_log(seed, tmpdir, essential_equipment: Iterable[str], other_equipment: Iterable[str]) -> Dict: - """Returns a parsed logs from `tlo.methods.healthsystem.summary` from a simulation object, in which a single - event has been scheduled with the specified equipment usage, and the availability of equipment has been manipulated. - """ + # Do the logging + eq_default.write_to_log() + + +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, essential_equipment: Iterable[str], other_equipment: Iterable[str] +) -> Dict: + """Returns the parsed logs from `tlo.methods.healthsystem.summary:EquipmentEverUsed_ByFacilityID` from a + simulation object in which a single event has been run with the specified equipment usage. The + availability of equipment has been manipulated so that the item_codes given in + `equipment_item_code_that_is_available` and `equipment_item_code_that_is_not_available` are as expected. """ class DummyHSIEvent(HSI_Event, IndividualScopeEventMixin): - def __init__(self, - module, - person_id, - level, - essential_equipment: Union[int, None], - other_equipment: Union[int, None] - ): + def __init__( + self, + module, + person_id, + level, + essential_equipment, + other_equipment, + ): super().__init__(module, person_id=person_id) self.TREATMENT_ID = "DummyHSIEvent" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = level - self.ESSENTIAL_EQUIPMENT = str(essential_equipment) if essential_equipment is not None else set() + self.add_equipment(essential_equipment) # Declaration at init will mean that these items are considered + # essential. self._other_equipment = other_equipment def apply(self, person_id, squeeze_factor): if self._other_equipment is not None: self.add_equipment(self._other_equipment) - class DummyModule(Module): METADATA = {Metadata.DISEASE_MODULE, Metadata.USES_HEALTHSYSTEM} @@ -58,11 +169,11 @@ def initialise_population(self, population): def initialise_simulation(self, sim): # Schedule the HSI_Event to occur on the first day of the simulation - sim.modules['HealthSystem'].schedule_hsi_event( + sim.modules["HealthSystem"].schedule_hsi_event( hsi_event=DummyHSIEvent( person_id=0, - level='2', - module=sim.modules['DummyModule'], + level="2", + module=sim.modules["DummyModule"], essential_equipment=self.essential_equipment, other_equipment=self.other_equipment, ), @@ -77,22 +188,22 @@ def initialise_simulation(self, sim): sim.register( demography.Demography(resourcefilepath=resourcefilepath), healthsystem.HealthSystem(resourcefilepath=resourcefilepath), - DummyModule(essential_equipment=essential_equipment, other_equipment=other_equipment), + DummyModule( + essential_equipment=essential_equipment, other_equipment=other_equipment + ), ) # Manipulate availability of equipment - df = sim.modules['HealthSystem'].parameters['Equipment'] - col_for_availability = df.columns[df.columns.str.startswith('Avail_')] - df.loc[df['Equip_Code'].isin(equipment_item_code_that_is_available), col_for_availability] = True - df.loc[df['Equip_Code'].isin(equipment_item_code_that_is_not_available), col_for_availability] = False - df['Equip_Item'] = df['Equip_Code'].astype(str) - sim.modules['HealthSystem'].parameters['Equipment'] = df.loc[df['Equip_Code'].isin(set(equipment_item_code_that_is_available) | set(equipment_item_code_that_is_not_available))] + df = sim.modules["HealthSystem"].parameters["equipment_availability_estimates"] + df.loc[df['Item_Code'].isin(equipment_item_code_that_is_available), 'Pr_Available'] = 1.0 + df.loc[df['Item_Code'].isin(equipment_item_code_that_is_not_available), 'Pr_Available'] = 0.0 + # Run the simulation sim.make_initial_population(n=100) - sim.simulate(end_date=pd.DateOffset(months=1)) - - return parse_log_file(sim.log_filepath)['tlo.methods.healthsystem.summary'] + sim.simulate(end_date=sim.start_date + pd.DateOffset(months=1)) + # Return the parsed log of `tlo.methods.healthsystem.summary` + return parse_log_file(sim.log_filepath)["tlo.methods.healthsystem.summary"] @@ -103,98 +214,108 @@ def test_equipment_use_is_logged(seed, tmpdir): * An HSI that declares use of equipment during its `apply` method (but no essential equipment); * An HSI that declare use of essential equipment but nothing in its `apply` method`; * An HSI that declare use of essential equipment and equipment during its `apply` method; - * An HSI that declares not use of any equipment (logs should be empty). + * An HSI that declares not use of any equipment. """ - - def logged_equipment_used(sim: Dict) -> pd.DataFrame: - """Read the log to work out what equipment usage has been logged.""" - # @Eva - I think this will somehow use the function that is currently in `src/scripts/healthsystem/equipment/equipment_catalogue.py` - pass - - def get_sim(essential_equipment, other_equipment): - """Pass-through to `run_simulation_return_log` to make call simpler.""" - return run_simulation_return_log( + the_item_code = equipment_item_code_that_is_available[0] + another_item_code = equipment_item_code_that_is_available[1] + + def all_equipment_ever_used(log: Dict) -> set: + """With the log of equipment used in the simulation, return a set of the equipments item that have been used + (at any facility).""" + s = set() + for i in log["EquipmentEverUsed_ByFacilityID"]['EquipmentEverUsed']: + s.update(eval(i)) + return s + + # * An HSI that declares no use of any equipment (logs should be empty). + assert set() == all_equipment_ever_used( + run_simulation_and_return_log( seed=seed, tmpdir=tmpdir, - essential_equipment=essential_equipment, - other_equipment=other_equipment, + essential_equipment=set(), + other_equipment=set(), ) - - # Check that the log matches expectation under each permutation - item_available_as_set_of_str = {str(equipment_item_code_that_is_available[0])} + ) # * An HSI that declares use of equipment during its `apply` method (but no essential equipment) - expected_df = pd.DataFrame() # <-- fill in what we expect it to look like - assert expected_df.equals(logged_equipment_used(get_sim( - essential_equipment={}, - other_equipment=item_available_as_set_of_str, - ))) + assert {the_item_code} == all_equipment_ever_used( + run_simulation_and_return_log( + seed=seed, + tmpdir=tmpdir, + essential_equipment=set(), + other_equipment=the_item_code, + ) + ) # * An HSI that declare use of essential equipment but nothing in its `apply` method`; - expected_df = pd.DataFrame() # <-- fill in what we expect it to look like - assert expected_df.equals(logged_equipment_used(get_sim( - essential_equipment=item_available_as_set_of_str, - other_equipment={}, - ))) + assert {the_item_code} == all_equipment_ever_used( + run_simulation_and_return_log( + seed=seed, + tmpdir=tmpdir, + essential_equipment=the_item_code, + other_equipment=set(), + ) + ) # * An HSI that declare use of essential equipment and equipment during its `apply` method; - expected_df = pd.DataFrame() # <-- fill in what we expect it to look like - assert expected_df.equals(logged_equipment_used(get_sim( - essential_equipment=item_available_as_set_of_str, - other_equipment=item_available_as_set_of_str, - ))) + assert {the_item_code, another_item_code} == all_equipment_ever_used( + run_simulation_and_return_log( + seed=seed, + tmpdir=tmpdir, + essential_equipment=the_item_code, + other_equipment=another_item_code, + ) + ) + - # * An HSI that declares not use of any equipment (logs should be empty). - expected_df = pd.DataFrame() # <-- fill in what we expect it to look like - assert expected_df.equals(logged_equipment_used(get_sim( - essential_equipment={}, - other_equipment={}, - ))) def test_hsi_does_not_run_if_essential_equipment_is_not_available(seed, tmpdir): """Check that an HSI which declares an item of equipment that is essential does run if that item is available and does not run if that item is not available.""" - def did_hsi_run(sim: Dict) -> bool: - """Read the log to work out if the Dummy HSI Event ran or not.""" - pass + def did_the_hsi_run(log: Dict) -> bool: + """Read the log to work out if the `DummyHSIEvent` ran or not.""" + it_did_run = len(log['hsi_event_counts'].iloc[0]['hsi_event_key_to_counts']) > 0 + it_did_not_run = len(log['never_ran_hsi_event_counts'].iloc[0]['never_ran_hsi_event_key_to_counts']) > 0 + + # Check that there if it did not run, it has had its "never_ran" function called + assert it_did_run != it_did_not_run + + # Return indication of whether it did run + return it_did_run - def get_sim(essential_equipment): - """Pass-through to `run_simulation_return_log` to make call simpler.""" - return run_simulation_return_log( - seed=seed, - tmpdir=tmpdir, - essential_equipment=essential_equipment, - other_equipment=None - ) # HSI_Event that requires equipment that is available --> will run - assert did_hsi_run( - get_sim( - essential_equipment=set(str(equipment_item_code_that_is_available[0])) + assert did_the_hsi_run( + run_simulation_and_return_log( + seed=seed, + tmpdir=tmpdir, + essential_equipment=equipment_item_code_that_is_available, + other_equipment=set(), ) ) # HSI_Event that requires equipment that is not available --> will not run - assert not did_hsi_run( - get_sim( - essential_equipment=set(str(equipment_item_code_that_is_not_available[0])) + assert not did_the_hsi_run( + run_simulation_and_return_log( + seed=seed, + tmpdir=tmpdir, + essential_equipment=equipment_item_code_that_is_not_available, + other_equipment=set(), ) ) def test_lookup_equipment_item_code_from_item_name(): + # todo incorporate into calls above, different forms of calling. pass + def test_lookup_equipment_item_code_from_pkg_name(): pass -def test_lookup_item_availability_by_hsi_event(): - pass def test_change_equipment_availability(): """Test that we can change the availability of equipment midway through the simulation.""" pass - - From 27779e67c130d24e5bedc42c796aa4d51936f649 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Fri, 10 May 2024 17:18:18 +0100 Subject: [PATCH 417/443] squash - basic outline of Equipment class --- .../ResourceFile_HealthSystem_parameters.csv | 4 +- .../ResourceFile_EquipmentCatalogue.csv | 4 +- src/tlo/methods/equipment.py | 100 +++++----- src/tlo/methods/healthsystem.py | 30 ++- src/tlo/methods/hsi_event.py | 22 ++- tests/test_equipment.py | 173 ++++++++++++++---- tests/test_healthsystem.py | 2 +- 7 files changed, 235 insertions(+), 100 deletions(-) diff --git a/resources/healthsystem/ResourceFile_HealthSystem_parameters.csv b/resources/healthsystem/ResourceFile_HealthSystem_parameters.csv index 25315a9917..6ca37170a7 100644 --- a/resources/healthsystem/ResourceFile_HealthSystem_parameters.csv +++ b/resources/healthsystem/ResourceFile_HealthSystem_parameters.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d9398dca0e70d64f0865f5f6d99ec0eec5baf9d2716ad037236959d3e7f479ed -size 632 +oid sha256:5df67165565fc88987f848e43363616e2ea4135de7c74d131e785ddc0178f123 +size 706 diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_EquipmentCatalogue.csv index 2f3ca59328..33ba052c64 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:0d21fb0f546325fd264b0598efd7afbea15ad48564ed28cb9df970509ce1d405 -size 33196 +oid sha256:3e151e16f7eea2ae61d2fa637c26449aa533ddc6a7f0d83aff495f5f6c9d1f8d +size 33201 diff --git a/src/tlo/methods/equipment.py b/src/tlo/methods/equipment.py index 2bb1f15267..5f561dd8aa 100644 --- a/src/tlo/methods/equipment.py +++ b/src/tlo/methods/equipment.py @@ -25,8 +25,9 @@ class Equipment: specified in the ResourceFile; if 'none', then let no equipment be ever be available; if 'all', then all equipment is always available. - If an item_code is requested that is not recognised (not included in `data`), a `UserWarning` is issued, and the - result returned is on the basis of the average availability of other consumables in that facility in that month. + :param `: `master_facilities_list`: The pd.DataFrame with line-list of all the facilities in the HealthSystem. + + If an item_code is referred that is not recognised (not included in `catalogue`), a `UserWarning` is issued. """ def __init__( @@ -47,15 +48,18 @@ def __init__( self._items_available: Dict = dict() # Will be the internal store of which items are available at each # facility_id. This is of the form {facility_id: {items_available}}. - self._record_of_equipment_used = defaultdict(Counter) # Will be the internal store of which items have been - # used at each facility_id. This is of the form - # {facility_id: {item_code: count}}. + self._record_of_equipment_used_by_facility_id = defaultdict(Counter) # Will be the internal store of which + # items have been used at each facility_id This is of the form {facility_id: {item_code: count}}. # Set up the internal stores of equipment items that are available, ready for calls. self._set_equipment_items_available(availability=availability) - # Set up internal lookup for item_descriptor -> item_code - self.item_code_lookup = self.catalogue.set_index('Description')['Item_Code'].to_dict() + # Set up internal lookup for item_descriptor -> item_code, and validating codes/desciptors + self.item_code_lookup = self.catalogue.set_index('Item_Description')['Item_Code'].to_dict() + self._all_item_descriptors = set(self.item_code_lookup.keys()) + self._all_item_codes = set(self.item_code_lookup.values()) + + self._history_of_items_checked = set() # All the arguments provided to parse_items def on_simulation_end(self): """Things to do when the simulation end: @@ -119,57 +123,58 @@ def _set_equipment_items_available(self, availability: str): lambda x: set(x[x].index.get_level_values("Item_Code")) ).to_dict() - def _parse_items(self, items: Union[int, str, Iterable[int | str]]) -> Set[int]: + def parse_items(self, items: Union[int, str, Iterable[int | str]]) -> Set[int]: """Parse equipment items specified as an item_code (integer), an item descriptor (string), or an iterable of - either, and return as a set of item_code (integers).""" - - def first_element_in_iterable(it: Iterable[Any]) -> Any | None: - for el in it: - return el - - if isinstance(items, str): - # Single descriptor - return set([self.item_code_lookup(items)]) - elif isinstance(items, int): - # Single item_code provided - return set([items]) - elif isinstance(items, Iterable) and len(items) == 0: - # Iterable of length 0 - return set() - elif isinstance(items, Iterable) and isinstance(first_element_in_iterable(items), str): - # Iterable of descriptors - return set(map(self.item_code_lookup, items)) - elif isinstance(items, Iterable) and isinstance(first_element_in_iterable(items), int): - # Iterable of item_cods - return set(items) + either, and return as a set of item_code (integers). For any item_code/descriptor not recognised, a + `UserWarning` is issued.""" + + def check_item_code_recognised(item_codes: set[int]): + if not item_codes.issubset(self._all_item_codes): + warnings.warn(f'Item code(s) "{item_codes}" not recognised.') + 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.') + + # Make into a set if it is not one already + if isinstance(items, (str, int)): + items = set([items]) else: - raise ValueError(f'Item_Code format not recognised: {items=}') + items = set(items) - if isinstance(items, int): - item_codes = set([items]) # If single int provided, place it into a list - elif isinstance(items, list): - item_codes = set(items) + items_are_ints = all(isinstance(element, int) for element in items) + if items_are_ints: + check_item_code_recognised(items) + # In the return, any unrecognised item_codes are silently ignored. + return items.intersection(self._all_item_codes) + else: + check_item_descriptors_recognised(items) # Warn for any unrecognised descriptors + # In the return, any unrecognised descriptors are silently ignored. + return set(filter(lambda item: item is not None, map(self.item_code_lookup.get, items))) def is_all_items_available( - self, item_codes: Union[int, str, Iterable[int | str]], facility_id: int + self, item_codes: Set[int], facility_id: int ) -> bool: """Determine if all equipments are available at the given facility_id (or from the default if the faciluty_id is not recognised). Returns True only if all items are available at the facility_id, otherwise returns False.""" - return self._parse_items(item_codes).issubset(self._items_available[facility_id]) - + try: + return item_codes.issubset(self._items_available[facility_id]) + except KeyError: + raise ValueError(f'Not recognised {facility_id=}') def record_use_of_equipment( - self, item_codes: Union[int, str, Iterable[int | str]], facility_id: int + self, item_codes: Set[int], facility_id: int ) -> None: """Update internal record of the usage of items at equipment at the specified facility_id.""" - self._record_of_equipment_used[facility_id].update(self._parse_items(item_codes)) + self._record_of_equipment_used_by_facility_id[facility_id].update(item_codes) def write_to_log(self) -> None: """Write to the log: - * Summary of the equipment that was _ever_ used at each facility_level - * For each facility_id, a set of the equipment items ever used. + * Summary of the equipment that was _ever_ used at each district/facility level. + Note that the info-level health system logger (key: `hsi_event_counts`) contains logging of the equipment used + in each HSI event (if further finer splits are needed). Alternatively, different aggregations could be created + here for the summary logger, using the same pattern as used here. """ mfl = self.master_facilities_list @@ -183,7 +188,7 @@ def set_of_keys_or_empty_set(x: Union[set, dict]): return None set_of_equipment_ever_used_at_each_facility_id = pd.Series({ - fac_id: set_of_keys_or_empty_set(self._record_of_equipment_used.get(fac_id, set())) + fac_id: set_of_keys_or_empty_set(self._record_of_equipment_used_by_facility_id.get(fac_id, set())) for fac_id in mfl['Facility_ID'] }, name='EquipmentEverUsed').astype(str) @@ -202,3 +207,14 @@ def set_of_keys_or_empty_set(x: Union[set, dict]): 'equipment items that are ever used.', 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 th at module. Note that all interaction with the `Equipment` module is using set of item_codes.""" + 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=}') + + return set(df.loc[df['Pkg_Name'] == pkg_name, 'Item_Code'].values) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index a0a3e6b96f..eb44735d55 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -205,6 +205,16 @@ class HealthSystem(Module): "the ResourceFile; if 'none', then let no equipment ever be available; if 'all', then all equipment is " "always available. NB. This parameter is over-ridden if an argument is provided to the module initialiser." ), + 'equip_availability_postSwitch': Parameter( + Types.STRING, + "What to assume about the availability of equipment after the switch (see `year_equip_availability_switch`" + "). The options for this are the same as `equip_availability`." + ), + 'year_equip_availability_switch': Parameter( + Types.INT, + "Year in which the assumption for `equip_availability` changes (The change happens on 1st January of that " + "year.)" + ), # Service Availability 'Service_Availability': Parameter( @@ -708,6 +718,18 @@ def initialise_simulation(self, sim): Date(self.parameters["year_cons_availability_switch"], 1, 1) ) + # Schedule an equipment availability switch + sim.schedule_event( + HealthSystemChangeParameters( + self, + parameters={ + 'equip_availability': self.parameters['equip_availability_postSwitch'] + } + ), + Date(self.parameters["year_equip_availability_switch"], 1, 1) + ) + + # Schedule a one-off rescaling of _daily_capabilities broken down by officer type and level. # This occurs on 1st January of the year specified in the parameters. sim.schedule_event(ConstantRescalingHRCapabilities(self), @@ -1628,7 +1650,6 @@ def record_hsi_event(self, hsi_event, actual_appt_footprint=None, squeeze_factor squeeze_factor=_squeeze_factor, did_run=did_run, priority=priority, - equipment=hsi_event.EQUIPMENT, ) def write_to_hsi_log( @@ -1639,9 +1660,9 @@ def write_to_hsi_log( squeeze_factor: float, did_run: bool, priority: int, - equipment: set, ): """Write the log `HSI_Event` and add to the summary counter.""" + # Debug logger gives simple line-list for every HSI event logger.debug( key="HSI_Event", data={ @@ -1654,16 +1675,19 @@ def write_to_hsi_log( 'did_run': did_run, 'Facility_Level': event_details.facility_level if event_details.facility_level is not None else -99, 'Facility_ID': facility_id if facility_id is not None else -99, - 'equipment': sorted(equipment), + 'Equipment': sorted(event_details.equipment), }, description="record of each HSI event" ) if did_run: if self._hsi_event_count_log_period is not None: + # Do logging for HSI Event using counts of each 'unique type' of HSI event (as defined by + # `HSIEventDetails`). event_details_key = self._hsi_event_details.setdefault( event_details, len(self._hsi_event_details) ) self._hsi_event_counts_log_period[event_details_key] += 1 + # Do logging for 'summary logger' self._summary_counter.record_hsi_event( treatment_id=event_details.treatment_id, hsi_event_name=event_details.event_name, diff --git a/src/tlo/methods/hsi_event.py b/src/tlo/methods/hsi_event.py index 8598cb1294..f828a47968 100644 --- a/src/tlo/methods/hsi_event.py +++ b/src/tlo/methods/hsi_event.py @@ -113,8 +113,6 @@ def __init__(self, module, *args, **kwargs): self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None - self.ESSENTIAL_EQUIPMENT = None - self.EQUIPMENT = set() # todo should this be private, and should we add setter methods self.TREATMENT_ID = "" self.ACCEPTED_FACILITY_LEVEL = None @@ -272,22 +270,26 @@ def make_appt_footprint(self, dict_of_appts) -> Counter: ) def add_equipment(self, item_codes: Union[int, str, Iterable[int | str]]): - """Declare that piece(s) of equipment are used in this HSI_Event.""" - self._EQUIPMENT.update(self.healthcare_system.equipment._parse_items(item_codes)) + """Declare that piece(s) of equipment are used in this HSI_Event. + Checks are done on the validity of the item_codes/item descriptions and warnings issued if they are not + recognised.""" + self._EQUIPMENT.update(self.healthcare_system.equipment.parse_items(item_codes)) @property def is_all_declared_equipment_available(self) -> bool: - """Returns True if all the declared items of equipment are available. This is called before the HSI is run - so is looking only at those items that are declared when this instance was created.""" + """Returns `True` if all the declared items of equipment are available. This is called before the HSI is run + and so is looking only at those items that are declared when this instance was created.""" return self.healthcare_system.equipment.is_all_items_available( item_codes=self._EQUIPMENT, facility_id=self.facility_info.id, ) - def is_equipment_available(self, item_codes: Union[int, Iterable[int]]) -> bool: - """Check whether all the equipment item_codes are available.""" + def is_equipment_available(self, item_codes: Union[int, str, Iterable[int | str]]) -> bool: + """Check whether all the equipment item_codes are available. This does not imply that the equipment is being + used and no logging happens. It is provided as a convenience to disease module authors in case the logic of + during an HSI Event depends on the availability of a piece of equipment.""" return self.healthcare_system.equipment.is_all_items_available( - item_codes=item_codes, + item_codes=self.healthcare_system.equipment.parse_items(item_codes), facility_id=self.facility_info.id, ) @@ -399,7 +401,7 @@ def as_namedtuple( beddays_footprint=tuple( sorted((k, v) for k, v in self.BEDDAYS_FOOTPRINT.items() if v > 0) ), - equipment=(tuple(sorted(self.EQUIPMENT))), # todo check if this is defined in HSIEventDetails and whether we want this here + equipment=(tuple(sorted(self._EQUIPMENT))), ) def post_apply_hook(self): diff --git a/tests/test_equipment.py b/tests/test_equipment.py index 96faf660f8..6dcc6c0699 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -25,10 +25,10 @@ def test_core_functionality_of_equipment_class(seed): # Create toy data catalogue = pd.DataFrame( [ - {"Description": "ItemZero", "Item_Code": 0}, - {"Description": "ItemOne", "Item_Code": 1}, - {"Description": "ItemTwo", "Item_Code": 2}, - {"Description": "ItemThree", "Item_Code": 3}, + {"Item_Description": "ItemZero", "Item_Code": 0, "Pkg_Name": float('nan')}, + {"Item_Description": "ItemOne", "Item_Code": 1, "Pkg_Name": float('nan')}, + {"Item_Description": "ItemTwo", "Item_Code": 2, "Pkg_Name": 'PkgWith2+3'}, + {"Item_Description": "ItemThree", "Item_Code": 3, "Pkg_Name": 'PkgWith2+3'}, ] ) data_availability = pd.DataFrame( @@ -61,23 +61,47 @@ def test_core_functionality_of_equipment_class(seed): master_facilities_list=mfl, availability="default", ) - # - using single integer for one item_code (item should be available) - assert eq_default.is_all_items_available(item_codes=1, facility_id=1) - # - using list of integers for item_codes (items should be available) - assert eq_default.is_all_items_available(item_codes=[1, 2], facility_id=1) - # - calling an item for which data on availability is not provided (should not raise error) - eq_default.is_all_items_available(item_codes=3, facility_id=1) - # - calling an item at a facility that for which data is not provided (should give average behaviour for other facilities) - assert not eq_default.is_all_items_available(item_codes=0, facility_id=2) - assert eq_default.is_all_items_available(item_codes=1, facility_id=2) - # - calling an item for which no data at a facility with no data (should not error) - eq_default.is_all_items_available(item_codes=3, facility_id=2) + + # Checks on parsing equipment items + # - using single integer for one item_code + assert {1} == eq_default.parse_items(1) + # - using list of integers for item_codes + assert {1, 2} == eq_default.parse_items([1, 2]) + # - using single string for one item descriptor + assert eq_default.parse_items('ItemOne') + # - using list of strings for item descriptors + assert eq_default.parse_items(['ItemOne', 'ItemTwo']) + # - an empty iterable of equipment should always be work whether expressed as list/tuple/set + assert set() == eq_default.parse_items(list()) + assert set() == eq_default.parse_items(tuple()) + assert set() == eq_default.parse_items(set()) + + # - Calling for unrecognised item_codes (should raise warning) + with pytest.warns(): + eq_default.parse_items(10001) + with pytest.warns(): + eq_default.parse_items('ItemThatIsNotDefined') + + # 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) + assert eq_default.is_all_items_available(item_codes={1, 2}, facility_id=1) # - calling when no items available (should be false) - assert not eq_default.is_all_items_available(item_codes=[0, 2], facility_id=0) + assert not eq_default.is_all_items_available(item_codes={0, 2}, facility_id=0) # - calling when some items available (should be false) - assert not eq_default.is_all_items_available(item_codes=[1, 2], facility_id=0) + assert not eq_default.is_all_items_available(item_codes={1, 2}, facility_id=0) + # - calling for empty set of equipment (should always be available) + assert eq_default.is_all_items_available(item_codes=set(), facility_id=0) + + # - calling an item for which data on availability is not provided (should not raise error) + eq_default.is_all_items_available(item_codes={3}, facility_id=1) + # - calling an item at a facility that for which data is not provided (should give average behaviour for other facilities) + assert not eq_default.is_all_items_available(item_codes={0}, facility_id=2) + assert eq_default.is_all_items_available(item_codes={1}, facility_id=2) + # - calling a recognised item for which no data at a facility with no data (should not error) + eq_default.is_all_items_available(item_codes={3}, facility_id=2) + # -- calling for an unrecognised facility_id (should error) + with pytest.raises(ValueError): + eq_default.is_all_items_available(item_codes={1}, facility_id=1001) # -- when using `none` availability behaviour: everything should not be available! eq_none = Equipment( @@ -88,11 +112,13 @@ def test_core_functionality_of_equipment_class(seed): master_facilities_list=mfl, ) # - calling when all items available (should be false because using 'none' behaviour) - assert not eq_none.is_all_items_available(item_codes=[1, 2], facility_id=1) + assert not eq_none.is_all_items_available(item_codes={1, 2}, facility_id=1) # - calling when no items available (should be false) - assert not eq_none.is_all_items_available(item_codes=[0, 2], facility_id=0) + assert not eq_none.is_all_items_available(item_codes={0, 2}, facility_id=0) # - calling when some items available (should be false) - assert not eq_none.is_all_items_available(item_codes=[1, 2], facility_id=0) + assert not eq_none.is_all_items_available(item_codes={1, 2}, facility_id=0) + # - calling for empty set of equipment (should always be available) + assert eq_none.is_all_items_available(item_codes=set(), facility_id=0) # -- when using `all` availability behaviour: everything should not be available! eq_all = Equipment( @@ -103,22 +129,29 @@ def test_core_functionality_of_equipment_class(seed): master_facilities_list=mfl, ) # - calling when all items available (should be true) - assert eq_all.is_all_items_available(item_codes=[1, 2], facility_id=1) + assert eq_all.is_all_items_available(item_codes={1, 2}, facility_id=1) # - calling when no items available (should be true because using 'all' behaviour) - assert eq_all.is_all_items_available(item_codes=[0, 2], facility_id=0) + assert eq_all.is_all_items_available(item_codes={0, 2}, facility_id=0) # - calling when some items available (should be true because using 'all' behaviour) - assert eq_all.is_all_items_available(item_codes=[1, 2], facility_id=0) + assert eq_all.is_all_items_available(item_codes={1, 2}, facility_id=0) + # - calling for empty set of equipment (should always be available) + assert eq_all.is_all_items_available(item_codes=set(), facility_id=0) # Check recording use of equipment # - Add records, using calls with integers and list to different facility_id - eq_default.record_use_of_equipment(item_codes=1, facility_id=0) - eq_default.record_use_of_equipment(item_codes=[0, 1], facility_id=0) - eq_default.record_use_of_equipment(item_codes=[0, 1], facility_id=1) + eq_default.record_use_of_equipment(item_codes={1}, facility_id=0) + eq_default.record_use_of_equipment(item_codes={0, 1}, facility_id=0) + eq_default.record_use_of_equipment(item_codes={0, 1}, facility_id=1) # - Check that internal record is as expected - assert dict(eq_default._record_of_equipment_used) == {0: {0: 1, 1: 2}, 1: {0: 1, 1: 1}} + assert dict(eq_default._record_of_equipment_used_by_facility_id) == {0: {0: 1, 1: 2}, 1: {0: 1, 1: 1}} + + # Lookup the item_codes that belong in a particular package. + # - When package is recognised + assert {2, 3} == eq_default.lookup_item_codes_from_pkg_name(pkg_name='PkgWith2+3') # 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='') - # Do the logging - eq_default.write_to_log() equipment_item_code_that_is_available = [0, 1, ] @@ -268,8 +301,6 @@ def all_equipment_ever_used(log: Dict) -> set: ) - - def test_hsi_does_not_run_if_essential_equipment_is_not_available(seed, tmpdir): """Check that an HSI which declares an item of equipment that is essential does run if that item is available and does not run if that item is not available.""" @@ -307,15 +338,77 @@ def did_the_hsi_run(log: Dict) -> bool: ) -def test_lookup_equipment_item_code_from_item_name(): - # todo incorporate into calls above, different forms of calling. - pass +def test_change_equipment_availability(seed): + """Test that we can change the availability of equipment midway through the simulation.""" + # Set-up simulation that starts with `all` availability and then changes to `none` after one year. In the + # simulation a DummyModule schedules a DummyHSI that runs every month and tries to get a piece of equipment. + # Check that this piece of equipment is available for the first year but not the second year of the simulation. + class DummyHSIEvent(HSI_Event, IndividualScopeEventMixin): + def __init__( + self, + module, + person_id, + ): + super().__init__(module, person_id=person_id) + self.TREATMENT_ID = "DummyHSIEvent" + self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) + self.ACCEPTED_FACILITY_LEVEL = '1a' + self.store_of_equipment_checks = dict() -def test_lookup_equipment_item_code_from_pkg_name(): - pass + def apply(self, person_id, squeeze_factor): + # Check availability of a piece of equipment, with item_code = 0 + self.store_of_equipment_checks.update( + { + self.sim.date: self.is_equipment_available(item_codes={0}) + } + ) + # Schedule recurrence of this event in one month's time + self.sim.modules["HealthSystem"].schedule_hsi_event( + hsi_event=self, + do_hsi_event_checks=False, + topen=self.sim.date + pd.DateOffset(months=1), + tclose=None, + priority=0, + ) -def test_change_equipment_availability(): - """Test that we can change the availability of equipment midway through the simulation.""" - pass + class DummyModule(Module): + METADATA = {Metadata.DISEASE_MODULE, Metadata.USES_HEALTHSYSTEM} + + def read_parameters(self, data_folder): + pass + + def initialise_population(self, population): + pass + + def initialise_simulation(self, sim): + # Schedule the HSI_Event to occur on the first day of the simulation (it will schedule its own repeats) + self.the_hsi_event = DummyHSIEvent(person_id=0, module=self) + + sim.modules["HealthSystem"].schedule_hsi_event( + hsi_event=self.the_hsi_event, + do_hsi_event_checks=False, + topen=sim.date, + tclose=None, + priority=0, + ) + + sim = Simulation(start_date=Date(2010, 1, 1), seed=seed) + sim.register( + demography.Demography(resourcefilepath=resourcefilepath), + healthsystem.HealthSystem(resourcefilepath=resourcefilepath), + DummyModule(), + ) + # Modify the parameters of the healthsystem to effect a change in the availability of equipment + sim.modules['HealthSystem'].parameters['equip_availability'] = 'all' + sim.modules['HealthSystem'].parameters['equip_availability_postSwitch'] = 'none' + sim.modules['HealthSystem'].parameters['year_equip_availability_switch'] = 2011 + + sim.make_initial_population(n=100) + sim.simulate(end_date=sim.start_date + pd.DateOffset(years=2)) + + # Get store & check for availabilities of the equipment + log = pd.Series(sim.modules['DummyModule'].the_hsi_event.store_of_equipment_checks) + assert log[log.index < Date(2011, 1, 1)].all() + assert not log[log.index >= Date(2011, 1, 1)].any() diff --git a/tests/test_healthsystem.py b/tests/test_healthsystem.py index 1bccb9e7c4..5dece9b2d9 100644 --- a/tests/test_healthsystem.py +++ b/tests/test_healthsystem.py @@ -948,7 +948,7 @@ def apply(self, person_id, squeeze_factor): detailed_consumables = log["tlo.methods.healthsystem"]['Consumables'] assert {'date', 'TREATMENT_ID', 'did_run', 'Squeeze_Factor', 'priority', 'Number_By_Appt_Type_Code', 'Person_ID', - 'Facility_Level', 'Facility_ID', 'Event_Name', 'equipment' + 'Facility_Level', 'Facility_ID', 'Event_Name', 'Equipment' } == set(detailed_hsi_event.columns) assert {'date', 'Frac_Time_Used_Overall', 'Frac_Time_Used_By_Facility_ID', 'Frac_Time_Used_By_OfficerType', } == set(detailed_capacity.columns) From 1988ae274846ed195d808af382b19eb2d9cf8d64 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Fri, 10 May 2024 17:33:52 +0100 Subject: [PATCH 418/443] linting --- src/tlo/methods/contraception.py | 3 ++- src/tlo/methods/equipment.py | 32 +++++++++++++++---------------- src/tlo/methods/healthsystem.py | 5 ++++- src/tlo/methods/hsi_event.py | 20 ++++++++++--------- src/tlo/methods/symptommanager.py | 3 ++- tests/test_equipment.py | 14 +++++++------- 6 files changed, 41 insertions(+), 36 deletions(-) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 67d6684fce..83fd86cab9 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -164,7 +164,8 @@ def read_parameters(self, data_folder): """Import the relevant sheets from the ResourceFile (excel workbook) and declare values for other parameters (CSV ResourceFile). """ - workbook = pd.read_excel(Path(self.resourcefilepath) / 'contraception' / 'ResourceFile_Contraception.xlsx', sheet_name=None) + workbook = pd.read_excel(Path(self.resourcefilepath) / 'contraception' / 'ResourceFile_Contraception.xlsx', + sheet_name=None) # Import selected sheets from the workbook as the parameters sheet_names = [ diff --git a/src/tlo/methods/equipment.py b/src/tlo/methods/equipment.py index 5f561dd8aa..3ba34eca4a 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 Dict, Set, Iterable, Union, Counter, Optional, Any +from typing import Counter, Dict, Iterable, Optional, Set, Union import numpy as np import pandas as pd @@ -45,21 +45,19 @@ def __init__( self.master_facilities_list = master_facilities_list # Create internal storage structures - self._items_available: Dict = dict() # Will be the internal store of which items are available at each + self._items_available: Dict = dict() # <-- Will be the internal store of which items are available at each # facility_id. This is of the form {facility_id: {items_available}}. - self._record_of_equipment_used_by_facility_id = defaultdict(Counter) # Will be the internal store of which + self._record_of_equipment_used_by_facility_id = defaultdict(Counter) # <-- Will be the internal store of which # items have been used at each facility_id This is of the form {facility_id: {item_code: count}}. - # Set up the internal stores of equipment items that are available, ready for calls. - self._set_equipment_items_available(availability=availability) - - # Set up internal lookup for item_descriptor -> item_code, and validating codes/desciptors - self.item_code_lookup = self.catalogue.set_index('Item_Description')['Item_Code'].to_dict() - self._all_item_descriptors = set(self.item_code_lookup.keys()) - self._all_item_codes = set(self.item_code_lookup.values()) + # 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._all_item_descriptors = set(self._item_code_lookup.keys()) + self._all_item_codes = set(self._item_code_lookup.values()) - self._history_of_items_checked = set() # All the arguments provided to parse_items + # Initialise the internal stores of equipment items that are available, ready for calls. + self._set_equipment_items_available(availability=availability) def on_simulation_end(self): """Things to do when the simulation end: @@ -67,14 +65,14 @@ def on_simulation_end(self): """ self.write_to_log() - def update_availability(self, availability) -> None: + def update_availability(self, availability: str) -> None: """Update the availability of equipment. This is expected to be called midway through the simulation if the assumption of the equipment availability needs to change.""" self._set_equipment_items_available(availability=availability) def _set_equipment_items_available(self, availability: str): """Update internal store of which items of equipment are available. This is called at the beginning of the - simulation and whenever an update in `availability` is needed.""" + simulation and whenever an update in `availability` is done by `update_availability`.""" # For any facility_id in the data all_fac_ids = self.master_facilities_list['Facility_ID'].unique() @@ -128,7 +126,7 @@ def parse_items(self, items: Union[int, str, Iterable[int | str]]) -> Set[int]: either, and return as a set of item_code (integers). For any item_code/descriptor not recognised, a `UserWarning` is issued.""" - def check_item_code_recognised(item_codes: set[int]): + 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.') @@ -145,18 +143,18 @@ def check_item_descriptors_recognised(item_descriptors: set[str]): items_are_ints = all(isinstance(element, int) for element in items) if items_are_ints: - check_item_code_recognised(items) + check_item_codes_recognised(items) # In the return, any unrecognised item_codes are silently ignored. return items.intersection(self._all_item_codes) else: check_item_descriptors_recognised(items) # Warn for any unrecognised descriptors # In the return, any unrecognised descriptors are silently ignored. - return set(filter(lambda item: item is not None, map(self.item_code_lookup.get, items))) + return set(filter(lambda item: item is not None, map(self._item_code_lookup.get, items))) def is_all_items_available( self, item_codes: Set[int], facility_id: int ) -> bool: - """Determine if all equipments are available at the given facility_id (or from the default if the faciluty_id + """Determine if all equipments are available at the given facility_id (or from the default if the facility_id is not recognised). Returns True only if all items are available at the facility_id, otherwise returns False.""" try: return item_codes.issubset(self._items_available[facility_id]) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index eb44735d55..28b7576aa0 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1894,7 +1894,10 @@ def on_end_of_year(self) -> None: # If we are at the end of the year preceeding the mode switch, and if wanted # to rescale capabilities to capture effective availability as was recorded, on # average, in the past year, do so here. - if (self.sim.date.year == self.parameters['year_mode_switch'] - 1) and self.parameters['scale_to_effective_capabilities']: + if ( + (self.sim.date.year == self.parameters['year_mode_switch'] - 1) + and self.parameters['scale_to_effective_capabilities'] + ): self._rescale_capabilities_to_capture_effective_capability() self._summary_counter.write_to_log_and_reset_counters() self.consumables.on_end_of_year() diff --git a/src/tlo/methods/hsi_event.py b/src/tlo/methods/hsi_event.py index f828a47968..487d4fdabb 100644 --- a/src/tlo/methods/hsi_event.py +++ b/src/tlo/methods/hsi_event.py @@ -1,7 +1,7 @@ from __future__ import annotations from collections import Counter -from typing import TYPE_CHECKING, Dict, Literal, NamedTuple, Optional, Set, Tuple, Union, Iterable +from typing import TYPE_CHECKING, Dict, Iterable, Literal, NamedTuple, Optional, Set, Tuple, Union import numpy as np @@ -172,12 +172,21 @@ def never_ran(self) -> None: logger.debug(key="message", data=f"{self.__class__.__name__}: was never run.") def post_apply_hook(self) -> None: - """Impose the bed-days footprint (if target of the HSI is a person_id)""" + """ + Do things following the event's `apply` function running. + * Impose the bed-days footprint (if target of the HSI is a person_id) + * Record the equipment that has been added before and during the course of the HSI Event. + """ if isinstance(self.target, int): self.healthcare_system.bed_days.impose_beddays_footprint( person_id=self.target, footprint=self.bed_days_allocated_to_this_event ) + self.healthcare_system.equipment.record_use_of_equipment( + item_codes=self._EQUIPMENT, + facility_id=self.facility_info.id + ) + def run(self, squeeze_factor): """Make the event happen.""" updated_appt_footprint = self.apply(self.target, squeeze_factor) @@ -404,13 +413,6 @@ def as_namedtuple( equipment=(tuple(sorted(self._EQUIPMENT))), ) - def post_apply_hook(self): - """Record the equipment that has been added before and during the course of the HSI Event.""" - self.healthcare_system.equipment.record_use_of_equipment( - item_codes=self._EQUIPMENT, - facility_id=self.facility_info.id - ) - class HSIEventWrapper(Event): """This is wrapper that contains an HSI event. 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/tests/test_equipment.py b/tests/test_equipment.py index 6dcc6c0699..44899f5c19 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -1,15 +1,13 @@ """This file contains all the tests to do with Equipment use logging and availability checks.""" import os -import time from pathlib import Path -from typing import Union, Dict, Iterable - -import pytest +from typing import Dict, Iterable import numpy as np import pandas as pd +import pytest -from tlo import Simulation, Module, Date, logging +from tlo import Date, Module, Simulation from tlo.analysis.utils import parse_log_file from tlo.events import IndividualScopeEventMixin from tlo.methods import Metadata, demography, healthsystem @@ -94,7 +92,8 @@ def test_core_functionality_of_equipment_class(seed): # - calling an item for which data on availability is not provided (should not raise error) eq_default.is_all_items_available(item_codes={3}, facility_id=1) - # - calling an item at a facility that for which data is not provided (should give average behaviour for other facilities) + # - calling an item at a facility that for which data is not provided (should give average behaviour for other + # facilities) assert not eq_default.is_all_items_available(item_codes={0}, facility_id=2) assert eq_default.is_all_items_available(item_codes={1}, facility_id=2) # - calling a recognised item for which no data at a facility with no data (should not error) @@ -147,7 +146,8 @@ def test_core_functionality_of_equipment_class(seed): # Lookup the item_codes that belong in a particular package. # - When package is recognised - assert {2, 3} == eq_default.lookup_item_codes_from_pkg_name(pkg_name='PkgWith2+3') # these items are in the same package + assert {2, 3} == eq_default.lookup_item_codes_from_pkg_name(pkg_name='PkgWith2+3') # 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='') From 13d5a3c6d6c3e26557d1e9f8f6d2ec981f1fcdfc Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Fri, 10 May 2024 17:39:08 +0100 Subject: [PATCH 419/443] update call in bed-days --- src/tlo/methods/healthsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 28b7576aa0..870735f27f 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2537,14 +2537,14 @@ def apply(self, population): treatment_id='Inpatient_Care', facility_level=self.module._facility_by_facility_id[_fac_id].level, appt_footprint=tuple(sorted(_inpatient_appts.items())), - beddays_footprint=() + beddays_footprint=(), + equipment=set(), ), person_id=-1, facility_id=_fac_id, squeeze_factor=0.0, priority=-1, did_run=True, - equipment=set(), # TODO: explore more, should it be non-empty in some cases? ) # Restart the total footprint of all calls today, beginning with those due to existing in-patients. From 3d4b80df68f0198ddfe35241cd428cef3d97e20b Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Sun, 12 May 2024 10:36:06 +0100 Subject: [PATCH 420/443] use hashable type in HSIEventDetails --- src/tlo/methods/healthsystem.py | 2 +- src/tlo/methods/hsi_event.py | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 870735f27f..95b90d44ac 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -2538,7 +2538,7 @@ def apply(self, population): facility_level=self.module._facility_by_facility_id[_fac_id].level, appt_footprint=tuple(sorted(_inpatient_appts.items())), beddays_footprint=(), - equipment=set(), + equipment=tuple(), # Equipment is normally a set, but this has to be hashable. ), person_id=-1, facility_id=_fac_id, diff --git a/src/tlo/methods/hsi_event.py b/src/tlo/methods/hsi_event.py index 487d4fdabb..61fdf9d188 100644 --- a/src/tlo/methods/hsi_event.py +++ b/src/tlo/methods/hsi_event.py @@ -182,10 +182,12 @@ def post_apply_hook(self) -> None: person_id=self.target, footprint=self.bed_days_allocated_to_this_event ) - self.healthcare_system.equipment.record_use_of_equipment( - item_codes=self._EQUIPMENT, - facility_id=self.facility_info.id - ) + if self.facility_info is not None: + # If there is a facility_info (e.g., healthsystem not running in disabled mode), then record equipment used + self.healthcare_system.equipment.record_use_of_equipment( + item_codes=self._EQUIPMENT, + facility_id=self.facility_info.id + ) def run(self, squeeze_factor): """Make the event happen.""" From 3bb2581707c20fe55b468133d257fed976a28b16 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Sun, 12 May 2024 11:30:12 +0100 Subject: [PATCH 421/443] update logic and add docstring --- src/tlo/methods/equipment.py | 72 +++++++++++++++++++++++++----------- src/tlo/methods/hsi_event.py | 37 +++++++++++------- tests/test_equipment.py | 8 ++-- 3 files changed, 78 insertions(+), 39 deletions(-) diff --git a/src/tlo/methods/equipment.py b/src/tlo/methods/equipment.py index 3ba34eca4a..f70aada5a2 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, Dict, Iterable, Optional, Set, Union +from typing import Counter, Iterable, Optional, Set, Union import numpy as np import pandas as pd @@ -14,6 +14,33 @@ class Equipment: """This is the Equipment Class. It maintains a current record of the availability of equipment in the HealthSystem. It is expected that this is instantiated by the `HealthSystem` module. + The basic paradigm is that an HSI_Event can declare equipment that is required for delivering the healthcare + service that the HSI_Event represents. The HSI_Event uses `self.add_equipment()` to make these declaration, with + reference to the items of equipment that are defined in `ResourceFile_EquipmentCatalogue.csv`. (These declaration + can be in the form of the descriptor or the equipment item code). These declarations can be used when the HSI_Event + is created but before it is run (in `__init__`), or during execution of the HSI_Event (in `apply`). + + As the HSI_Event can declare equipment that is required before it is run, the HealthSystem _can_ use this to + prevent an HSI_Event running if the equipment declared is not available. Note that for equipment that is declared + whilst the HSI_Event is running, there are no checks on availabilty, and the HSI_Event is allowed to continue + running even if equipment is declared that is not available. For this reason, HSI_Event should declare equipment + that is _essential_ for the healthcare service in its `__init__` method. If the logic inside the `__apply__` method + of the HSI_Event depends on the availability of equipment, then the events can find the probability with which + item(s) will be available to it, using `self.probability_equipment_available()`. + + The data on the availability of equipment data refers to the proportion of facilities in a district of a + particular level (i.e., the Facility_ID) that do have that piece of equipment. In the model, we do not know + which facility the person is attending (there are many actual facilities grouped together into one Facility_ID in + the model). Therefore, the determination of whether equipment is available is made probabilistically for the + HSI_Event (i.e., the probability that the actual facility being attended by the person has the equipment is + represented by the proportion of such facilities that do have that equipment). It is assumed that the probabilities + of each item being available are independent of one other (so that the probability of all items being available is + the product of the probabilities of each item). This probabilistic determination of availability is only done + _once_ for the HSI_Event: i.e., if the equipment is not available for the instance of the HSI_Event, then it will + remain not available if the same event is re-scheduled/re-entered into the HealthSystem queue. This represents + that if the facility that a particular person attends for the HSI_Event does not have the equipment available, then + it will not be available on another day. + :param: 'catalogue': The database of all recognised item_codes. :param: `data_availability`: Specifies the probability with which each equipment (identified by an `item_code`) is @@ -45,18 +72,18 @@ def __init__( self.master_facilities_list = master_facilities_list # Create internal storage structures - self._items_available: Dict = dict() # <-- Will be the internal store of which items are available at each - # facility_id. This is of the form {facility_id: {items_available}}. - - self._record_of_equipment_used_by_facility_id = defaultdict(Counter) # <-- Will be the internal store of which - # items have been used at each facility_id This is of the form {facility_id: {item_code: count}}. + # - Probabilities of items being available at each facility_id + self._probabilities_of_items_available = pd.DataFrame() + # - Internal store of which items have been used at each facility_id This is of the form + # {facility_id: {item_code: count}}. + self._record_of_equipment_used_by_facility_id = defaultdict(Counter) # <-- Will be the - # Data structures for quick look-ups for items and descriptors + # - 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._all_item_descriptors = set(self._item_code_lookup.keys()) self._all_item_codes = set(self._item_code_lookup.values()) - # Initialise the internal stores of equipment items that are available, ready for calls. + # Initialise the internal stores of the probability with which equipment items that are available. self._set_equipment_items_available(availability=availability) def on_simulation_end(self): @@ -111,15 +138,7 @@ def _set_equipment_items_available(self, availability: str): else: raise KeyError(f"Unknown equipment availability specified: {availability}") - # Sample these probability to find which items are actually available - is_available = df > self.rng.random(size=len(df)) - - # Organise into dict of set, of the form: {facility_id: {items_available}} for known facility_ids - # (N.B. Has to be done this way around in order to guarantee that we have each known facility_id in the keys - # even if there are no item available.) - self._items_available: Dict = is_available.groupby("Facility_ID").agg( - lambda x: set(x[x].index.get_level_values("Item_Code")) - ).to_dict() + self._probabilities_of_items_available = df def parse_items(self, items: Union[int, str, Iterable[int | str]]) -> Set[int]: """Parse equipment items specified as an item_code (integer), an item descriptor (string), or an iterable of @@ -151,16 +170,25 @@ def check_item_descriptors_recognised(item_descriptors: set[str]): # In the return, any unrecognised descriptors are silently ignored. return set(filter(lambda item: item is not None, map(self._item_code_lookup.get, items))) - def is_all_items_available( + def probability_equipment_available( self, item_codes: Set[int], facility_id: int - ) -> bool: - """Determine if all equipments are available at the given facility_id (or from the default if the facility_id - is not recognised). Returns True only if all items are available at the facility_id, otherwise returns False.""" + ) -> float: + """Returns the probability that all the equipment item_codes are available. It does so by looking at the + probabilities of each equipment item being available and multiplying these together to find the probability + that _all_ are available.""" try: - return item_codes.issubset(self._items_available[facility_id]) + return self._probabilities_of_items_available.loc[(facility_id, list(item_codes))].prod() except KeyError: raise ValueError(f'Not recognised {facility_id=}') + def is_all_items_available( + self, item_codes: Set[int], facility_id: int + ) -> bool: + """Determine if all equipments are available at the given facility_id. Returns True only if all items are + available at the facility_id, otherwise returns False.""" + return self.rng.random_sample() < self.probability_equipment_available(item_codes=item_codes, + facility_id=facility_id) + def record_use_of_equipment( self, item_codes: Set[int], facility_id: int ) -> None: diff --git a/src/tlo/methods/hsi_event.py b/src/tlo/methods/hsi_event.py index 61fdf9d188..cd00451087 100644 --- a/src/tlo/methods/hsi_event.py +++ b/src/tlo/methods/hsi_event.py @@ -109,10 +109,11 @@ def __init__(self, module, *args, **kwargs): self.module = module super().__init__(*args, **kwargs) - # Information that will later be received about this HSI + # Information that will later be received/computed about this HSI self._received_info_about_bed_days = None self.expected_time_requests = {} self.facility_info = None + self._is_all_declared_equipment_available = None self.TREATMENT_ID = "" self.ACCEPTED_FACILITY_LEVEL = None @@ -288,18 +289,28 @@ def add_equipment(self, item_codes: Union[int, str, Iterable[int | str]]): @property def is_all_declared_equipment_available(self) -> bool: - """Returns `True` if all the declared items of equipment are available. This is called before the HSI is run - and so is looking only at those items that are declared when this instance was created.""" - return self.healthcare_system.equipment.is_all_items_available( - item_codes=self._EQUIPMENT, - facility_id=self.facility_info.id, - ) - - def is_equipment_available(self, item_codes: Union[int, str, Iterable[int | str]]) -> bool: - """Check whether all the equipment item_codes are available. This does not imply that the equipment is being - used and no logging happens. It is provided as a convenience to disease module authors in case the logic of - during an HSI Event depends on the availability of a piece of equipment.""" - return self.healthcare_system.equipment.is_all_items_available( + """Returns `True` if all the declared items of equipment are available. This is called by the HealthSystem + before the HSI is run and so is looking only at those items that are declared when this instance was created. + The evaluation of whether equipment is available is only done _once_ for this instance of the event: i.e., if + the equipment is not available for the instance of the HSI_Event, then it will remain not available if the + same event is re-scheduled/re-entered into the HealthSystem queue. This is representing that if the facility + that a particular person attends for the HSI_Event does not have the equipment available, then it will not + be available on another day.""" + + if self._is_all_declared_equipment_available is None: + # Availability has not already been evaluated: determine availability + self._is_all_declared_equipment_available = self.healthcare_system.equipment.is_all_items_available( + item_codes=self._EQUIPMENT, + facility_id=self.facility_info.id, + ) + return self._is_all_declared_equipment_available + + def probability_equipment_available(self, item_codes: Union[int, str, Iterable[int | str]]) -> float: + """Returns the probability that all the equipment item_codes are available. This does not imply that the + equipment is being used and no logging happens. It is provided as a convenience to disease module authors in + case the logic of during an HSI Event depends on the availability of a piece of equipment. This function + accepts the item codes/descriptions in a variety of formats, so this needs to be parsed.""" + return self.healthcare_system.equipment.probability_equipment_available( item_codes=self.healthcare_system.equipment.parse_items(item_codes), facility_id=self.facility_info.id, ) diff --git a/tests/test_equipment.py b/tests/test_equipment.py index 44899f5c19..45884a4034 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -339,7 +339,7 @@ def did_the_hsi_run(log: Dict) -> bool: def test_change_equipment_availability(seed): - """Test that we can change the availability of equipment midway through the simulation.""" + """Test that we can change the probability of the availability of equipment midway through the simulation.""" # Set-up simulation that starts with `all` availability and then changes to `none` after one year. In the # simulation a DummyModule schedules a DummyHSI that runs every month and tries to get a piece of equipment. # Check that this piece of equipment is available for the first year but not the second year of the simulation. @@ -360,7 +360,7 @@ def apply(self, person_id, squeeze_factor): # Check availability of a piece of equipment, with item_code = 0 self.store_of_equipment_checks.update( { - self.sim.date: self.is_equipment_available(item_codes={0}) + self.sim.date: self.probability_equipment_available(item_codes={0}) } ) @@ -410,5 +410,5 @@ def initialise_simulation(self, sim): # Get store & check for availabilities of the equipment log = pd.Series(sim.modules['DummyModule'].the_hsi_event.store_of_equipment_checks) - assert log[log.index < Date(2011, 1, 1)].all() - assert not log[log.index >= Date(2011, 1, 1)].any() + assert (1.0 == log[log.index < Date(2011, 1, 1)]).all() + assert (0.0 == log[log.index >= Date(2011, 1, 1)]).all() From 3c16898ea90809aab8e9c6fd73e4bd0df29f77f7 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 09:34:27 +0100 Subject: [PATCH 422/443] linting --- src/tlo/methods/equipment.py | 96 +++++++++++++++++++----------------- src/tlo/methods/hsi_event.py | 27 +++++----- 2 files changed, 66 insertions(+), 57 deletions(-) diff --git a/src/tlo/methods/equipment.py b/src/tlo/methods/equipment.py index f70aada5a2..50bd866856 100644 --- a/src/tlo/methods/equipment.py +++ b/src/tlo/methods/equipment.py @@ -14,47 +14,54 @@ class Equipment: """This is the Equipment Class. It maintains a current record of the availability of equipment in the HealthSystem. It is expected that this is instantiated by the `HealthSystem` module. - The basic paradigm is that an HSI_Event can declare equipment that is required for delivering the healthcare - service that the HSI_Event represents. The HSI_Event uses `self.add_equipment()` to make these declaration, with - reference to the items of equipment that are defined in `ResourceFile_EquipmentCatalogue.csv`. (These declaration - can be in the form of the descriptor or the equipment item code). These declarations can be used when the HSI_Event - is created but before it is run (in `__init__`), or during execution of the HSI_Event (in `apply`). + The basic paradigm is that an `HSI_Event` can declare equipment that is required for delivering the healthcare + service that the `HSI_Event` represents. The `HSI_Event` uses `self.add_equipment()` to make these declaration, + with reference to the items of equipment that are defined in `ResourceFile_EquipmentCatalogue.csv`. (These + declaration can be in the form of the descriptor or the equipment item code). These declarations can be used when + the `HSI_Event` is created but before it is run (in `__init__`), or during execution of the HSI_Event (in `apply`). As the HSI_Event can declare equipment that is required before it is run, the HealthSystem _can_ use this to prevent an HSI_Event running if the equipment declared is not available. Note that for equipment that is declared - whilst the HSI_Event is running, there are no checks on availabilty, and the HSI_Event is allowed to continue - running even if equipment is declared that is not available. For this reason, HSI_Event should declare equipment - that is _essential_ for the healthcare service in its `__init__` method. If the logic inside the `__apply__` method - of the HSI_Event depends on the availability of equipment, then the events can find the probability with which - item(s) will be available to it, using `self.probability_equipment_available()`. + whilst the HSI_Event is running, there are no checks on availability, and the HSI_Event is allowed to continue + running even if equipment is declared is not available. For this reason, the `HSI_Event` should declare equipment + that is _essential_ for the healthcare service in its `__init__` method. If the logic inside the `apply` method + of the `HSI_Event` depends on the availability of equipment, then it can find the probability with which + item(s) will be available using `self.probability_equipment_available()`. The data on the availability of equipment data refers to the proportion of facilities in a district of a - particular level (i.e., the Facility_ID) that do have that piece of equipment. In the model, we do not know - which facility the person is attending (there are many actual facilities grouped together into one Facility_ID in - the model). Therefore, the determination of whether equipment is available is made probabilistically for the - HSI_Event (i.e., the probability that the actual facility being attended by the person has the equipment is - represented by the proportion of such facilities that do have that equipment). It is assumed that the probabilities - of each item being available are independent of one other (so that the probability of all items being available is - the product of the probabilities of each item). This probabilistic determination of availability is only done - _once_ for the HSI_Event: i.e., if the equipment is not available for the instance of the HSI_Event, then it will - remain not available if the same event is re-scheduled/re-entered into the HealthSystem queue. This represents - that if the facility that a particular person attends for the HSI_Event does not have the equipment available, then - it will not be available on another day. + particular level (i.e., the `Facility_ID`) that do have that piece of equipment. In the model, we do not know + which actual facility the person is attending (there are many actual facilities grouped together into one + `Facility_ID` in the model). Therefore, the determination of whether equipment is available is made + probabilistically for the `HSI_Event` (i.e., the probability that the actual facility being attended by the + person has the equipment is represented by the proportion of such facilities that do have that equipment). It is + assumed that the probabilities of each item being available are independent of one other (so that the + probability of all items being available is the product of the probabilities for each item). This probabilistic + determination of availability is only done _once_ for the `HSI_Event`: i.e., if the equipment is determined to + not be available for the instance of the `HSI_Event`, then it will remain not available if the same event is + re-scheduled / re-entered into the HealthSystem queue. This represents that if the facility that a particular + person attends for the `HSI_Event` does not have the equipment available, then it will still not be available on + another day. + + Where data on availability is not provided for an item, the probability of availability is inferred from the + average availability of other items in that `Facility_ID`. Likewise, the probability of an item being available + at `Facility_ID` is inferred from the average availability of that item at other facilities. If an item_code is + referred that is not recognised (not included in `catalogue`), a `UserWarning` is issued. If a facility_id is + referred that is not recognised (not included in `master_facilities_list`), an `Error` is raised. :param: 'catalogue': The database of all recognised item_codes. :param: `data_availability`: Specifies the probability with which each equipment (identified by an `item_code`) is - available at a facility level. Note that information is not necessarily provided for every item in the `catalogue`. + available at a facility level. Note that information is not necessarily provided for every item in the `catalogue` + or every facility_id in the `master_facilities_list`. :param: `rng`: The Random Number Generator object to use for random numbers. :param: `availability`: Determines the mode availability of the equipment. If 'default' then use the availability - specified in the ResourceFile; if 'none', then let no equipment be ever be available; if 'all', then all + specified in the `data_availability`; if 'none', then let no equipment be ever be available; if 'all', then all equipment is always available. - :param `: `master_facilities_list`: The pd.DataFrame with line-list of all the facilities in the HealthSystem. + :param `: `master_facilities_list`: The pd.DataFrame with the line-list of all the facilities in the HealthSystem. - If an item_code is referred that is not recognised (not included in `catalogue`), a `UserWarning` is issued. """ def __init__( @@ -87,27 +94,27 @@ def __init__( self._set_equipment_items_available(availability=availability) def on_simulation_end(self): - """Things to do when the simulation end: + """Things to do when the simulation ends: * Log (to the summary logger) the equipment that has been used. """ self.write_to_log() def update_availability(self, availability: str) -> None: """Update the availability of equipment. This is expected to be called midway through the simulation if - the assumption of the equipment availability needs to change.""" + the assumption of the equipment availability is changed.""" self._set_equipment_items_available(availability=availability) def _set_equipment_items_available(self, availability: str): - """Update internal store of which items of equipment are available. This is called at the beginning of the - simulation and whenever an update in `availability` is done by `update_availability`.""" + """Update internal store of probabilities of items of equipment being available. This is called at the beginning + of the simulation and whenever an update in `availability` is done by `update_availability`.""" - # For any facility_id in the data + # All facility_id in the simulation all_fac_ids = self.master_facilities_list['Facility_ID'].unique() # All equipment items in the catalogue all_eq_items = self.catalogue["Item_Code"].unique() - # Create full dataset, where we force that there is probability of availability for every item_code at every + # Create "full" dataset, where we force that there is probability of availability for every item_code at every # observed facility df = pd.Series( index=pd.MultiIndex.from_product( @@ -120,7 +127,7 @@ def _set_equipment_items_available(self, availability: str): ] ) - # Merge in original dataset and use the mean in that facility_id to impute availability of missing item_code + # Merge in original dataset and use the mean in that facility_id to impute availability of missing item_codes df = df.groupby("Facility_ID").transform(lambda x: x.fillna(x.mean())) # ... and also impute availability for any facility_ids for which no data, based on all other facilities df = df.groupby("Item_Code").transform(lambda x: x.fillna(x.mean())) @@ -132,12 +139,13 @@ def _set_equipment_items_available(self, availability: str): if availability == "default": pass elif availability == "all": - df = (df + 1).clip(upper=1.0) + df = (df + 1).clip(upper=1.0) # All probabilities -> 1.0 elif availability == "none": - df = df.mul(0.0) + df = df.mul(0.0) # All probabilities -> 0.0 else: - raise KeyError(f"Unknown equipment availability specified: {availability}") + raise KeyError(f"Unknown equipment availability specified: {availability=}") + # Save self._probabilities_of_items_available = df def parse_items(self, items: Union[int, str, Iterable[int | str]]) -> Set[int]: @@ -170,7 +178,7 @@ def check_item_descriptors_recognised(item_descriptors: set[str]): # In the return, any unrecognised descriptors are silently ignored. return set(filter(lambda item: item is not None, map(self._item_code_lookup.get, items))) - def probability_equipment_available( + def probability_all_equipment_available( self, item_codes: Set[int], facility_id: int ) -> float: """Returns the probability that all the equipment item_codes are available. It does so by looking at the @@ -184,10 +192,10 @@ def probability_equipment_available( def is_all_items_available( self, item_codes: Set[int], facility_id: int ) -> bool: - """Determine if all equipments are available at the given facility_id. Returns True only if all items are + """Determine if all equipment items are available at the given facility_id. Returns True only if all items are available at the facility_id, otherwise returns False.""" - return self.rng.random_sample() < self.probability_equipment_available(item_codes=item_codes, - facility_id=facility_id) + return self.rng.random_sample() < self.probability_all_equipment_available(item_codes=item_codes, + facility_id=facility_id) def record_use_of_equipment( self, item_codes: Set[int], facility_id: int @@ -199,8 +207,8 @@ def write_to_log(self) -> None: """Write to the log: * Summary of the equipment that was _ever_ used at each district/facility level. Note that the info-level health system logger (key: `hsi_event_counts`) contains logging of the equipment used - in each HSI event (if further finer splits are needed). Alternatively, different aggregations could be created - here for the summary logger, using the same pattern as used here. + in each HSI event (if finer splits are needed). Alternatively, different aggregations could be created here for + the summary logger, using the same pattern as used here. """ mfl = self.master_facilities_list @@ -211,7 +219,7 @@ def set_of_keys_or_empty_set(x: Union[set, dict]): elif isinstance(x, dict): return set(x.keys()) else: - return None + return set() set_of_equipment_ever_used_at_each_facility_id = pd.Series({ fac_id: set_of_keys_or_empty_set(self._record_of_equipment_used_by_facility_id.get(fac_id, set())) @@ -229,7 +237,7 @@ def set_of_keys_or_empty_set(x: Union[set, dict]): for _, row in output.iterrows(): logger_summary.info( key='EquipmentEverUsed_ByFacilityID', - description='For each facility_id (the set of facilities of the same level in a district), the set' + description='For each facility_id (the set of facilities of the same level in a district), the set of' 'equipment items that are ever used.', data=row.to_dict(), ) @@ -237,7 +245,7 @@ def set_of_keys_or_empty_set(x: Union[set, 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 th at module. Note that all interaction with the `Equipment` module is using set of item_codes.""" + saved on the module.""" df = self.catalogue if pkg_name not in df['Pkg_Name'].unique(): diff --git a/src/tlo/methods/hsi_event.py b/src/tlo/methods/hsi_event.py index cd00451087..64d2ee5930 100644 --- a/src/tlo/methods/hsi_event.py +++ b/src/tlo/methods/hsi_event.py @@ -281,21 +281,22 @@ def make_appt_footprint(self, dict_of_appts) -> Counter: "values" ) - def add_equipment(self, item_codes: Union[int, str, Iterable[int | str]]): - """Declare that piece(s) of equipment are used in this HSI_Event. - Checks are done on the validity of the item_codes/item descriptions and warnings issued if they are not + def add_equipment(self, item_codes: Union[int, str, Iterable[int | str]]) -> None: + """Declare that piece(s) of equipment are used in this HSI_Event. Equipment items can be identified by their + item_codes (int) or descriptors (str); a singular item or an iterable of items can be defined at once. Checks + are done on the validity of the item_codes/item descriptions and a warning issued if any are not recognised.""" self._EQUIPMENT.update(self.healthcare_system.equipment.parse_items(item_codes)) @property def is_all_declared_equipment_available(self) -> bool: - """Returns `True` if all the declared items of equipment are available. This is called by the HealthSystem - before the HSI is run and so is looking only at those items that are declared when this instance was created. - The evaluation of whether equipment is available is only done _once_ for this instance of the event: i.e., if - the equipment is not available for the instance of the HSI_Event, then it will remain not available if the - same event is re-scheduled/re-entered into the HealthSystem queue. This is representing that if the facility - that a particular person attends for the HSI_Event does not have the equipment available, then it will not - be available on another day.""" + """Returns `True` if all the (currently) declared items of equipment are available. This is called by the + `HealthSystem` module before the HSI is run and so is looking only at those items that are declared when this + instance was created. The evaluation of whether equipment is available is only done _once_ for this instance of + the event: i.e., if the equipment is not available for the instance of this `HSI_Event`, then it will remain not + available if the same event is re-scheduled/re-entered into the HealthSystem queue. This is representing that + if the facility that a particular person attends for the HSI_Event does not have the equipment available, then + it will also not be available on another day.""" if self._is_all_declared_equipment_available is None: # Availability has not already been evaluated: determine availability @@ -308,9 +309,9 @@ def is_all_declared_equipment_available(self) -> bool: def probability_equipment_available(self, item_codes: Union[int, str, Iterable[int | str]]) -> float: """Returns the probability that all the equipment item_codes are available. This does not imply that the equipment is being used and no logging happens. It is provided as a convenience to disease module authors in - case the logic of during an HSI Event depends on the availability of a piece of equipment. This function - accepts the item codes/descriptions in a variety of formats, so this needs to be parsed.""" - return self.healthcare_system.equipment.probability_equipment_available( + case the logic during an `HSI_Event` depends on the availability of a piece of equipment. This function + accepts the item codes/descriptions in a variety of formats, so the argument needs to be parsed.""" + return self.healthcare_system.equipment.probability_all_equipment_available( item_codes=self.healthcare_system.equipment.parse_items(item_codes), facility_id=self.facility_info.id, ) From 5bcdb3885dd7e81f925a95bd5be82ff900167731 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 09:54:29 +0100 Subject: [PATCH 423/443] linting --- src/tlo/methods/healthsystem.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index 95b90d44ac..f6ced798b9 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1105,8 +1105,9 @@ def get_beds_availability(self) -> str: return _beds_availability def get_equip_availability(self) -> str: - """Returns equipment availability. (Should be equal to what is specified by the parameter, but overwrite with - what was provided in argument if an argument was specified -- provided for backward compatibility/debugging.)""" + """Returns equipment availability. (Should be equal to what is specified by the parameter, but can be + overwritten with what was provided in argument if an argument was specified -- provided for backward + compatibility/debugging.)""" if self.arg_equip_availability is None: _equip_availability = self.parameters['equip_availability'] @@ -1894,10 +1895,7 @@ def on_end_of_year(self) -> None: # If we are at the end of the year preceeding the mode switch, and if wanted # to rescale capabilities to capture effective availability as was recorded, on # average, in the past year, do so here. - if ( - (self.sim.date.year == self.parameters['year_mode_switch'] - 1) - and self.parameters['scale_to_effective_capabilities'] - ): + if (self.sim.date.year == self.parameters['year_mode_switch'] - 1) and self.parameters['scale_to_effective_capabilities']: self._rescale_capabilities_to_capture_effective_capability() self._summary_counter.write_to_log_and_reset_counters() self.consumables.on_end_of_year() @@ -1962,7 +1960,7 @@ def run_individual_level_events_in_mode_0_or_1(self, # Mode 0: All HSI Event run, with no squeeze # Mode 1: All HSI Events run with squeeze provided latter is not inf ok_to_run = True - # todo - also consider whether essential equipment is available + if self.mode_appt_constraints == 1 and squeeze_factor == float('inf'): ok_to_run = False From 3a8d3da97dad990ed5a12e0acc1a2181077e0a5e Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 10:06:38 +0100 Subject: [PATCH 424/443] linting --- tests/test_equipment.py | 50 ++++++++++++++++++++--------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/test_equipment.py b/tests/test_equipment.py index 45884a4034..061fbf2526 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -1,4 +1,4 @@ -"""This file contains all the tests to do with Equipment use logging and availability checks.""" +"""This file contains all the tests to do with Equipment.""" import os from pathlib import Path from typing import Dict, Iterable @@ -158,9 +158,9 @@ def test_core_functionality_of_equipment_class(seed): equipment_item_code_that_is_not_available = [2, 3,] def run_simulation_and_return_log( - seed, tmpdir, essential_equipment: Iterable[str], other_equipment: Iterable[str] + seed, tmpdir, equipment_in_init, equipment_in_apply ) -> Dict: - """Returns the parsed logs from `tlo.methods.healthsystem.summary:EquipmentEverUsed_ByFacilityID` from a + """Returns the parsed logs from `tlo.methods.healthsystem.summary` from a simulation object in which a single event has been run with the specified equipment usage. The availability of equipment has been manipulated so that the item_codes given in `equipment_item_code_that_is_available` and `equipment_item_code_that_is_not_available` are as expected. """ @@ -222,7 +222,7 @@ def initialise_simulation(self, sim): demography.Demography(resourcefilepath=resourcefilepath), healthsystem.HealthSystem(resourcefilepath=resourcefilepath), DummyModule( - essential_equipment=essential_equipment, other_equipment=other_equipment + essential_equipment=equipment_in_init, other_equipment=equipment_in_apply ), ) @@ -253,7 +253,7 @@ def test_equipment_use_is_logged(seed, tmpdir): another_item_code = equipment_item_code_that_is_available[1] def all_equipment_ever_used(log: Dict) -> set: - """With the log of equipment used in the simulation, return a set of the equipments item that have been used + """With the log of equipment used in the simulation, return a set of the equipment item that have been used (at any facility).""" s = set() for i in log["EquipmentEverUsed_ByFacilityID"]['EquipmentEverUsed']: @@ -265,45 +265,45 @@ def all_equipment_ever_used(log: Dict) -> set: run_simulation_and_return_log( seed=seed, tmpdir=tmpdir, - essential_equipment=set(), - other_equipment=set(), + equipment_in_init=set(), + equipment_in_apply=set(), ) ) - # * An HSI that declares use of equipment during its `apply` method (but no essential equipment) + # * An HSI that declares use of equipment only during its `apply` method. assert {the_item_code} == all_equipment_ever_used( run_simulation_and_return_log( seed=seed, tmpdir=tmpdir, - essential_equipment=set(), - other_equipment=the_item_code, + equipment_in_init=set(), + equipment_in_apply=the_item_code, ) ) - # * An HSI that declare use of essential equipment but nothing in its `apply` method`; + # * An HSI that declare use of equipment only in its `__init__` method assert {the_item_code} == all_equipment_ever_used( run_simulation_and_return_log( seed=seed, tmpdir=tmpdir, - essential_equipment=the_item_code, - other_equipment=set(), + equipment_in_init=the_item_code, + equipment_in_apply=set(), ) ) - # * An HSI that declare use of essential equipment and equipment during its `apply` method; + # * An HSI that declare use of equipment in `__init__` _and_ `apply`. assert {the_item_code, another_item_code} == all_equipment_ever_used( run_simulation_and_return_log( seed=seed, tmpdir=tmpdir, - essential_equipment=the_item_code, - other_equipment=another_item_code, + equipment_in_init=the_item_code, + equipment_in_apply=another_item_code, ) ) -def test_hsi_does_not_run_if_essential_equipment_is_not_available(seed, tmpdir): - """Check that an HSI which declares an item of equipment that is essential does run if that item is available - and does not run if that item is not available.""" +def test_hsi_does_not_run_if_equipment_declared_in_init_is_not_available(seed, tmpdir): + """Check that an HSI which declares an item of equipment that is declared in the HSI_Event's __init__ does run if + that item is available and does not run if that item is not available.""" def did_the_hsi_run(log: Dict) -> bool: """Read the log to work out if the `DummyHSIEvent` ran or not.""" @@ -322,8 +322,8 @@ def did_the_hsi_run(log: Dict) -> bool: run_simulation_and_return_log( seed=seed, tmpdir=tmpdir, - essential_equipment=equipment_item_code_that_is_available, - other_equipment=set(), + equipment_in_init=equipment_item_code_that_is_available, + equipment_in_apply=set(), ) ) @@ -332,8 +332,8 @@ def did_the_hsi_run(log: Dict) -> bool: run_simulation_and_return_log( seed=seed, tmpdir=tmpdir, - essential_equipment=equipment_item_code_that_is_not_available, - other_equipment=set(), + equipment_in_init=equipment_item_code_that_is_not_available, + equipment_in_apply=set(), ) ) @@ -341,8 +341,8 @@ def did_the_hsi_run(log: Dict) -> bool: def test_change_equipment_availability(seed): """Test that we can change the probability of the availability of equipment midway through the simulation.""" # Set-up simulation that starts with `all` availability and then changes to `none` after one year. In the - # simulation a DummyModule schedules a DummyHSI that runs every month and tries to get a piece of equipment. - # Check that this piece of equipment is available for the first year but not the second year of the simulation. + # simulation a DummyModule schedules a DummyHSI that runs every month and tries to get a piece of equipment; + # then, check that the probability that this piece of equipment is available each month during the simulation. class DummyHSIEvent(HSI_Event, IndividualScopeEventMixin): def __init__( From dc9d9b99f068f93c621617244da69dd5db70046c Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 11:17:42 +0100 Subject: [PATCH 425/443] linting --- src/tlo/methods/healthsystem.py | 5 ++++- tests/test_equipment.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/tlo/methods/healthsystem.py b/src/tlo/methods/healthsystem.py index f6ced798b9..f4c22f5ead 100644 --- a/src/tlo/methods/healthsystem.py +++ b/src/tlo/methods/healthsystem.py @@ -1895,7 +1895,10 @@ def on_end_of_year(self) -> None: # If we are at the end of the year preceeding the mode switch, and if wanted # to rescale capabilities to capture effective availability as was recorded, on # average, in the past year, do so here. - if (self.sim.date.year == self.parameters['year_mode_switch'] - 1) and self.parameters['scale_to_effective_capabilities']: + if ( + (self.sim.date.year == self.parameters['year_mode_switch'] - 1) + and self.parameters['scale_to_effective_capabilities'] + ): self._rescale_capabilities_to_capture_effective_capability() self._summary_counter.write_to_log_and_reset_counters() self.consumables.on_end_of_year() diff --git a/tests/test_equipment.py b/tests/test_equipment.py index 061fbf2526..7a679fa611 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -1,7 +1,7 @@ """This file contains all the tests to do with Equipment.""" import os from pathlib import Path -from typing import Dict, Iterable +from typing import Dict import numpy as np import pandas as pd From ea4027bc9ecb5fec06bd256332373761c5913863 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 11:20:13 +0100 Subject: [PATCH 426/443] provide default for 'equip_availability' in test_alri:get_sim() --- tests/test_alri.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_alri.py b/tests/test_alri.py index fb03312515..0fba5fea8d 100644 --- a/tests/test_alri.py +++ b/tests/test_alri.py @@ -54,7 +54,7 @@ def _get_person_id(df, age_bounds: tuple = (0.0, np.inf)) -> int: ].index[0] -def get_sim(tmpdir, seed, cons_available, equip_available): +def get_sim(tmpdir, seed, cons_available, equip_available='all'): """Return simulation objection with Alri and other necessary modules registered.""" sim = Simulation( start_date=start_date, From 2dd360ae18e439b009ee122b525565652eb6441e Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 11:29:16 +0100 Subject: [PATCH 427/443] roll back incidental changes --- ...rceFile_Consumables_Items_and_Packages.csv | 4 +- ...rceFile_Consumables_availability_small.csv | 4 +- .../ResourceFile_consumables_matched.csv | 2 +- .../ResourceFile_Equipment.csv | 3 - ...ate_consumables_item_codes_and_packages.py | 37 +-- .../equipment/equipment_catalogue.py | 255 ------------------ src/tlo/analysis/codes_to_items_list.py | 75 ------ src/tlo/analysis/utils.py | 6 +- tests/test_healthsystem.py | 2 +- 9 files changed, 8 insertions(+), 380 deletions(-) delete mode 100644 resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv delete mode 100644 src/scripts/healthsystem/equipment/equipment_catalogue.py delete mode 100644 src/tlo/analysis/codes_to_items_list.py diff --git a/resources/healthsystem/consumables/ResourceFile_Consumables_Items_and_Packages.csv b/resources/healthsystem/consumables/ResourceFile_Consumables_Items_and_Packages.csv index dd855930bf..8af8f070b2 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:153332e93cf11d2435cf9f3d0cd4fcca8b405785b75a02757a6d19425f62318c -size 253579 +oid sha256:85e2c3ba8037e74490751fbb8384709dff1907c785c856f0394f40b4fc024da3 +size 253400 diff --git a/resources/healthsystem/consumables/ResourceFile_Consumables_availability_small.csv b/resources/healthsystem/consumables/ResourceFile_Consumables_availability_small.csv index 2e745530ac..54453cbc2f 100644 --- a/resources/healthsystem/consumables/ResourceFile_Consumables_availability_small.csv +++ b/resources/healthsystem/consumables/ResourceFile_Consumables_availability_small.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:746e8bbdb48854349998b671536d9eed86862d824f50de28fd0242c006993c55 -size 6087715 +oid sha256:69a5143c0b7307c7bb48726aa73d6c2f61de2a69aeb445eec87494cf9d4a1041 +size 6087331 diff --git a/resources/healthsystem/consumables/ResourceFile_consumables_matched.csv b/resources/healthsystem/consumables/ResourceFile_consumables_matched.csv index f0da7695b5..7754d65118 100644 --- a/resources/healthsystem/consumables/ResourceFile_consumables_matched.csv +++ b/resources/healthsystem/consumables/ResourceFile_consumables_matched.csv @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cdc77f0229a2bf37e9629edd54cb5df80aa00b5bde7b94c74adfecc4f836e3bd +oid sha256:fbfe91222d3a2a32ed44a4be711b30c5323276a71df802f6c9249eb4c21f8d43 size 90158 diff --git a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv b/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv deleted file mode 100644 index da80c3c028..0000000000 --- a/resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:d4ded21bb2e84bc43de26d8d2eed50a183d8445c08fff3019b0fd647932dc20d -size 33323 diff --git a/src/scripts/data_file_processing/healthsystem/consumables/processing_data_from_one_health/generate_consumables_item_codes_and_packages.py b/src/scripts/data_file_processing/healthsystem/consumables/processing_data_from_one_health/generate_consumables_item_codes_and_packages.py index 83411a4f9e..3fcbccf9e2 100644 --- a/src/scripts/data_file_processing/healthsystem/consumables/processing_data_from_one_health/generate_consumables_item_codes_and_packages.py +++ b/src/scripts/data_file_processing/healthsystem/consumables/processing_data_from_one_health/generate_consumables_item_codes_and_packages.py @@ -21,8 +21,7 @@ # Set local Dropbox source path_to_dropbox = Path( # <-- point to the TLO dropbox locally - #'/Users/tbh03/Dropbox (SPH Imperial College)/Thanzi la Onse Theme 1 SHARE' - '/Users/sm2511/Dropbox/Thanzi la Onse') + '/Users/tbh03/Dropbox (SPH Imperial College)/Thanzi la Onse Theme 1 SHARE') resourcefilepath = Path("./resources") path_for_new_resourcefiles = resourcefilepath / "healthsystem/consumables" @@ -329,40 +328,6 @@ def add_record(df: pd.DataFrame, record: Dict): }, ) - -cons = cons.append({ - 'Intervention_Cat': "Added by SM (Recommended by EJ)", - 'Intervention_Pkg': "Misc", - 'Intervention_Pkg_Code': -99, - 'Items': "Cystoscope", - 'Item_Code': 285, - 'Expected_Units_Per_Case': 1.0, - 'Unit_Cost': np.nan}, - ignore_index=True -) - -cons = cons.append({ - 'Intervention_Cat': "Added by SM (Recommended by EJ)", - 'Intervention_Pkg': "Misc", - 'Intervention_Pkg_Code': -99, - 'Items': "Endoscope", - 'Item_Code': 280, - 'Expected_Units_Per_Case': 1.0, - 'Unit_Cost': np.nan}, - ignore_index=True -) - -cons = cons.append({ - 'Intervention_Cat': "Added by SM (Recommended by EJ)", - 'Intervention_Pkg': "Misc", - 'Intervention_Pkg_Code': -99, - 'Items': "Prostate specific antigen test", - 'Item_Code': 281, - 'Expected_Units_Per_Case': 1.0, - 'Unit_Cost': np.nan}, - ignore_index=True -) - # -------------- # -------------- # -------------- diff --git a/src/scripts/healthsystem/equipment/equipment_catalogue.py b/src/scripts/healthsystem/equipment/equipment_catalogue.py deleted file mode 100644 index f076e03509..0000000000 --- a/src/scripts/healthsystem/equipment/equipment_catalogue.py +++ /dev/null @@ -1,255 +0,0 @@ -import argparse -import warnings -from pathlib import Path - -import pandas as pd - -from tlo.analysis.utils import extract_results - -# TODO: make these to be arguments of called fnc -# %%% TO SET %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# Declare whether to scale the counts to Malawi population size -# (True/False) -do_scaling = True -# Declare as a list by which hsi event details you want the equipment be grouped in the catalogue (choose any number) -# (event details: 'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint') -catalog_by_details = ['treatment_id', 'facility_level'] -# Declare which time period you want the equipment be grouped in the catalogue (choose only one) -# (periods: 'monthly', 'annual') -catalog_by_time = 'annual' -# Suffix for output file names -suffix_file_names = '__2y_2Kpop_4runs_1draw' -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -# TODO: Could I have use the bin_hsi_event_details from src/tlo/analysis/utils.py instead? If so, how? -def get_monthly_hsi_event_counts(results_folder: Path) -> pd.DataFrame: - """Returned pd.DataFrame gives the monthly counts of all the hsi event details logged (details as keys) - for each simulated month. - NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" - - def get_hsi_event_counts(_df): - """Get the counts of all the hsi event details logged.""" - - def unpack_dict_in_series(_raw: pd.Series): - # Create an empty DataFrame to store the data - df = pd.DataFrame() - - # Iterate through the dictionary items - for _, mydict in _raw.items(): - for date, inner_dict in mydict.items(): - # Convert the inner_dict to a list of dictionaries with 'date' - data = [{'date': date, 'event_details_key': inner_dict_key, 'count': inner_dict_set} for - inner_dict_key, inner_dict_set in inner_dict.items()] - # Create a DataFrame from the list with date & fac_level as indexes - temp_df = pd.DataFrame(data) - temp_df.set_index(['date', 'event_details_key'], inplace=True) - temp_df.columns = [None] - - # Concatenate the temporary DataFrame to the result DataFrame - df = pd.concat([df, temp_df]) - - df.columns = [None] - - return df - - return _df \ - .set_index('date') \ - .pipe(unpack_dict_in_series) \ - .stack() \ - .droplevel(level=2) - - return extract_results( - results_folder, - module='tlo.methods.healthsystem.summary', - key='hsi_event_counts', - custom_generate_series=get_hsi_event_counts, - do_scaling=do_scaling - ) - - -def get_hsi_event_keys_all_runs(results_folder: Path) -> pd.DataFrame: - """Returned pd.DataFrame gives the dictionaries of hsi_event_details for each draw and run. - NB. 'healthsystem.summary' logger required to have been set at the level INFO or higher.""" - - def get_hsi_event_keys(_df): - """Get the hsi_event_keys for one particular run.""" - return _df['hsi_event_key_to_event_details'] - - return extract_results( - results_folder, - module='tlo.methods.healthsystem.summary', - key='hsi_event_details', - custom_generate_series=get_hsi_event_keys - ) - - -def create_equipment_catalogues(results_folder: Path, output_folder: Path): - # %%% Verify inputs are as expected - assert isinstance(do_scaling, bool), "The input parameter 'do_scaling' must be a boolean (True or False)" - assert isinstance(catalog_by_details, list), "The input parameter 'catalog_by_details' must be a list" - event_details = \ - {'event_name', 'module_name', 'treatment_id', 'facility_level', 'appt_footprint', 'beddays_footprint'} - for item in catalog_by_details: - assert isinstance(item, str) and item in event_details, \ - f"Each element in the input list 'catalog_by_details' must be a string and be one of the details:\n" \ - f"{event_details}" - assert catalog_by_time in {'monthly', 'annual'}, \ - "The input parameter 'catalog_by_time' must be one of the strings ('monthly' or 'annual')" - # --- - - # %%% Set output file names - # detailed CSV name - output_detailed_file_name = 'equipment_monthly_counts__all_event_details' + suffix_file_names + '.csv' - # requested details only CSV name - time_index = 'year' if catalog_by_time == 'annual' else 'date' - output_focused_file_name = \ - 'equipment_' + catalog_by_time + '_counts__by_' + time_index + '_' + '_'.join(catalog_by_details) + \ - suffix_file_names + '.csv' - output_summary_file_name = 'equipment_summary__module_name_event_name_treatment_id' + suffix_file_names + '.csv' - # --- - - # %%% Load RF - # Equipment - equip_resource_items_pkgs_df = pd.read_csv( - 'resources/healthsystem/infrastructure_and_equipment/ResourceFile_Equipment.csv' - ) - - # %% Catalog equipment by all HSI event details - sim_equipment = get_monthly_hsi_event_counts(results_folder) - sim_equipment_df = pd.DataFrame(sim_equipment) - hsi_event_keys = get_hsi_event_keys_all_runs(results_folder) - - final_df = pd.DataFrame() - - def details_col_to_str(details_col): - return details_col.apply(lambda x: ', '.join(map(str, x))) - - def get_equip_item_name_from_item_code(equip_item_code: int) -> str: - """Helper function to provide the equip item name (a string) when provided with the equip_item_code (an int).""" - lookup_df = equip_resource_items_pkgs_df - return str(pd.unique(lookup_df.loc[lookup_df["Equip_Code"] == equip_item_code, "Equip_Item"])[0]) - - def get_equip_item_code_from_item_name(equip_item_name: str) -> int: - """Helper function to provide the equip item code (an int) when provided with the equip_item_name (a string)""" - lookup_df = equip_resource_items_pkgs_df - return int(pd.unique(lookup_df.loc[lookup_df["Equip_Item"] == equip_item_name, "Equip_Code"])[0]) - - def lists_of_equip_item_codes_to_strings_of_list_of_equip_item_names(list_of_equip_item_codes_col): - return list_of_equip_item_codes_col.apply( - lambda x: - str(sorted([get_equip_item_name_from_item_code(item_code) for item_code in x])) - ) - - def strings_of_list_to_lists_of_strings(strings_of_list_col): - lists_of_strings_col = strings_of_list_col.apply(lambda x: x.strip('][').split("'")) - return lists_of_strings_col.apply(lambda x: [s for s in x if (s != '' and s != ', ')]) - - for col in hsi_event_keys.columns: - df_col = sim_equipment_df[col].dropna() - decoded_keys = df_col.index.get_level_values(1).astype(str).map(hsi_event_keys.at[0, col]) - - # %%% Verify the keys in dictionary and dataframe for the run 'col' are same - # Check if all keys in hsi_event_keys_set are in the 'event_details_key' of df_col - hsi_event_keys_set = set(hsi_event_keys.at[0, col].keys()) - missing_keys_df =\ - [key for key in hsi_event_keys_set if key not in df_col.index.get_level_values('event_details_key')] - - # Check if all keys in the 'event_details_key' of df_col are in hsi_event_keys_set - missing_keys_dict =\ - [key for key in df_col.index.get_level_values('event_details_key') if key not in hsi_event_keys_set] - - # Warn if some keys are missing - if missing_keys_df: - warnings.warn(UserWarning(f"Keys missing in sim_equipment_df for the run {col}: {missing_keys_df}")) - - if missing_keys_dict: - warnings.warn(UserWarning(f"Keys missing in hsi_event_keys for the run {col}: {missing_keys_dict}")) - # %%% - - df_col = pd.concat([df_col, pd.DataFrame(decoded_keys.tolist(), index=df_col.index)], axis=1) - # Make values in 'appt_footprint', 'beddays_footprint', and 'equipment' columns to be string - df_col['appt_footprint'] = details_col_to_str(df_col['appt_footprint']) - df_col['beddays_footprint'] = details_col_to_str(df_col['beddays_footprint']) - df_col['equipment'] = lists_of_equip_item_codes_to_strings_of_list_of_equip_item_names(df_col['equipment']) - df_col = (df_col.droplevel(level=1) - .set_index(['module_name', 'event_name', 'treatment_id', 'facility_level', 'appt_footprint', - 'beddays_footprint', 'equipment'], append=True)) - final_df = pd.concat([final_df, df_col], axis=1) - - # Replace NaN with 0 - final_df.fillna(0, inplace=True) - final_df.sort_index(inplace=True) - # Save the detailed equipment catalogue - final_df.to_csv(output_folder / output_detailed_file_name) - print(f'{output_detailed_file_name} saved.') - # --- - - # %% Catalog equipment summary - equipment_summary = final_df.copy() - equipment_summary = equipment_summary.groupby(['module_name', 'event_name', 'treatment_id', 'equipment']).sum() - equipment_summary = \ - equipment_summary.reset_index().set_index(['module_name', 'event_name', 'treatment_id', 'equipment']) - # Save the summary equipment catalogue - equipment_summary.index.to_frame().to_csv(output_folder / output_summary_file_name, index=False) - print(f'{output_summary_file_name} saved.') - # --- - - # %% Catalog equipment by requested details - equipment_counts_by_time_and_requested_details = final_df.copy() - - # Sum counts for each equipment set with the same date, treatment id, and facility level - # (remaining indexes removed), keeping only non-empty 'equipment' indexes - to_be_grouped_by = ['date'] + catalog_by_details + ['equipment'] - equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( - to_be_grouped_by, - dropna=True - ).sum() - - if catalog_by_time == 'annual': - # Sum counts annually - equipment_counts_by_time_and_requested_details['year'] = \ - equipment_counts_by_time_and_requested_details.index.get_level_values('date').year - equipment_counts_by_time_and_requested_details.set_index('year', append=True, inplace=True) - equipment_counts_by_time_and_requested_details.index.droplevel('date') - to_be_grouped_by = ['year'] + catalog_by_details + ['equipment'] - equipment_counts_by_time_and_requested_details = equipment_counts_by_time_and_requested_details.groupby( - to_be_grouped_by - ).sum() - - # Remove rows with no equipment used - equipment_counts_by_time_and_requested_details.drop("[]", level='equipment', axis=0, inplace=True) - # Split the equipment by an item per row - equipment_counts_by_time_and_requested_details['equipment'] = \ - equipment_counts_by_time_and_requested_details.index.get_level_values('equipment') - equipment_counts_by_time_and_requested_details.index = \ - equipment_counts_by_time_and_requested_details.index.droplevel('equipment') - equipment_counts_by_time_and_requested_details['equipment'] = strings_of_list_to_lists_of_strings( - equipment_counts_by_time_and_requested_details['equipment'] - ) - exploded_df = equipment_counts_by_time_and_requested_details.explode('equipment') - # Add column with equip item code - exploded_df['equip_code'] = exploded_df['equipment'].apply(lambda x: get_equip_item_code_from_item_name(x)) - exploded_df = exploded_df.set_index(['equipment', 'equip_code'], append=True) - - # Sum values with the same multi-index - exploded_df = exploded_df.groupby(level=exploded_df.index.names).sum() - - # Save the equipment counts CSV - exploded_df.to_csv(output_folder / output_focused_file_name) - print(f'{output_focused_file_name} saved.') - # --- - - return 0 - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("results_folder", type=Path) - args = parser.parse_args() - - create_equipment_catalogues( - results_folder=args.results_folder, - output_folder=args.results_folder, - ) -# NB. Edit run configuration, the Parameters: "./outputs/sejjej5@ucl.ac.uk/long_run_all_diseases-2023-09-04T233551Z" diff --git a/src/tlo/analysis/codes_to_items_list.py b/src/tlo/analysis/codes_to_items_list.py deleted file mode 100644 index da169a06e5..0000000000 --- a/src/tlo/analysis/codes_to_items_list.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -(1) Can be used for a list of items without item codes yet saved in a csv file named 'csv_file_to_update_name'. - -This script will assign unique code to each unique item name which has no code assigned yet. The codes are -assigned in order from the sequence 0, 1, 2, .... - -Duplicated items are allowed, the same code will be assigned to the same items. - -(2) Can be used when new items are added later without item codes but some items with codes are already in the list. - -This script will keep the existing codes for items with already assigned code and for items without existing -code will assign new code (continue in sequence, i.e. if the highest code is 5, it assigns new codes from the continuing -sequence 6, 7, 8, ...). - ------- -NB. Make sure the 'csv_file_to_update_name' is the file you want to update. The output will be named -'csv_file_to_update_name' + '_new.csv' to avoid unintentionally losing the previous version. ------- -""" - -from pathlib import Path - -import pandas as pd - -# Get the path of the current script file -script_path = Path(__file__) -print(script_path) - -# ############################# -# ## CHANGE THIS FOR YOUR FILE -# Specify name of the csv file -csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' -# Specify the file path to csv file -file_path = script_path.parent.parent.parent.parent / 'resources/healthsystem/infrastructure_and_equipment' -# Specify the names of columns containing the item names and item codes -item_col_name = 'Equip_Item' -code_col_name = 'Equip_Code' -# ############################# - -# Load the CSV RF into a DataFrame -df = pd.read_csv(Path(file_path) / str(csv_file_to_update_name + '.csv')) - -# Find unique values in Equipment that have no code and are not None or empty -unique_values =\ - df.loc[df[code_col_name].isna() & df[item_col_name].notna() & (df[item_col_name] != ''), item_col_name].unique() - -# Create a mapping of unique values to codes -value_to_code = {} -# Initialize the starting code value -if not df[code_col_name].isna().all(): - next_code = int(df[code_col_name].max()) + 1 -else: - next_code = 0 - -# Iterate through unique values -for value in unique_values: - # Check if there is at least one existing code for this value - matching_rows = df.loc[df[item_col_name] == value, code_col_name].dropna() - if not matching_rows.empty: - # Use the existing code for this value - existing_code = int(matching_rows.iloc[0]) - # TODO: verify all the codes are the same - else: - # If no existing codes, start with the next available code - existing_code = next_code - next_code += 1 - value_to_code[value] = existing_code - # Update the code_col_name column for matching rows - df.loc[df[item_col_name] == value, code_col_name] = existing_code - -# Convert code_col_name column to integers -df[code_col_name] = df[code_col_name].astype('Int64') # Convert to nullable integer type - -# Save CSV with equipment codes -df.to_csv(Path(file_path) / str(csv_file_to_update_name + '_new.csv'), index=False) diff --git a/src/tlo/analysis/utils.py b/src/tlo/analysis/utils.py index 64ba966aa5..3aeff2bb11 100644 --- a/src/tlo/analysis/utils.py +++ b/src/tlo/analysis/utils.py @@ -18,7 +18,6 @@ import numpy as np import pandas as pd import squarify -from pandas.api.types import is_numeric_dtype from tlo import Date, Simulation, logging, util from tlo.logging.reader import LogData @@ -294,10 +293,7 @@ def generate_series(dataframe: pd.DataFrame) -> pd.Series: df: pd.DataFrame = load_pickled_dataframes(results_folder, draw, run, module)[module][key] output_from_eval: pd.Series = generate_series(df) assert pd.Series == type(output_from_eval), 'Custom command does not generate a pd.Series' - if is_numeric_dtype(output_from_eval): - res[draw_run] = output_from_eval * get_multiplier(draw, run) - else: - res[draw_run] = output_from_eval + res[draw_run] = output_from_eval * get_multiplier(draw, run) except KeyError: # Some logs could not be found - probably because this run failed. diff --git a/tests/test_healthsystem.py b/tests/test_healthsystem.py index af18bb6670..5c52a907e4 100644 --- a/tests/test_healthsystem.py +++ b/tests/test_healthsystem.py @@ -948,7 +948,7 @@ def apply(self, person_id, squeeze_factor): detailed_consumables = log["tlo.methods.healthsystem"]['Consumables'] assert {'date', 'TREATMENT_ID', 'did_run', 'Squeeze_Factor', 'priority', 'Number_By_Appt_Type_Code', 'Person_ID', - 'Facility_Level', 'Facility_ID', 'Event_Name', 'equipment' + 'Facility_Level', 'Facility_ID', 'Event_Name', } == set(detailed_hsi_event.columns) assert {'date', 'Frac_Time_Used_Overall', 'Frac_Time_Used_By_Facility_ID', 'Frac_Time_Used_By_OfficerType', } == set(detailed_capacity.columns) From a306e245cfcc21b95cea3c2c2febe443f2f5ae78 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 11:36:01 +0100 Subject: [PATCH 428/443] remove calls to `self.set_equipment_essential_to_run_event({''})`` --- src/tlo/methods/alri.py | 2 +- src/tlo/methods/bladder_cancer.py | 10 +++---- src/tlo/methods/breast_cancer.py | 8 ++--- src/tlo/methods/cardio_metabolic_disorders.py | 10 +++---- .../methods/care_of_women_during_pregnancy.py | 30 +++++++++---------- src/tlo/methods/contraception.py | 2 +- src/tlo/methods/copd.py | 2 +- src/tlo/methods/depression.py | 10 +++---- src/tlo/methods/diarrhoea.py | 6 ++-- src/tlo/methods/epi.py | 2 +- src/tlo/methods/epilepsy.py | 4 +-- src/tlo/methods/hiv.py | 10 +++---- src/tlo/methods/labour.py | 8 ++--- src/tlo/methods/malaria.py | 10 +++---- src/tlo/methods/measles.py | 2 +- src/tlo/methods/newborn_outcomes.py | 4 +-- src/tlo/methods/oesophagealcancer.py | 8 ++--- src/tlo/methods/other_adult_cancers.py | 8 ++--- src/tlo/methods/postnatal_supervisor.py | 2 +- src/tlo/methods/prostate_cancer.py | 12 ++++---- src/tlo/methods/rti.py | 22 +++++++------- src/tlo/methods/schisto.py | 6 ++-- src/tlo/methods/stunting.py | 2 +- src/tlo/methods/tb.py | 14 ++++----- 24 files changed, 97 insertions(+), 97 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index 4b72309ce4..7e117997a9 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2316,7 +2316,7 @@ def __init__(self, module: Module, person_id: int, facility_level: str = "0", in self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels - self.set_equipment_essential_to_run_event({''}) + self.is_followup_following_treatment_failure = is_followup_following_treatment_failure diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 01cf3e8d72..5ade59c714 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -693,7 +693,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + # equipment: (ultrsound guided) biopsy, lab equipment for histology @@ -770,7 +770,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + # equipment: (ultrsound guided) biopsy, lab equipment for histology @@ -852,7 +852,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - self.set_equipment_essential_to_run_event({''}) + # equipment: standard equipment for surgery @@ -925,7 +925,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - self.set_equipment_essential_to_run_event({''}) + # I assume ultrasound (Ultrasound scanning machine) and biopsy @@ -984,7 +984,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - self.set_equipment_essential_to_run_event({''}) + # no equipment as far as I am aware diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 46072ed8e1..1c6b3b518b 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -672,7 +672,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - self.set_equipment_essential_to_run_event({''}) + # TODO: Eva's dummy equipment example (not sure if it actually needs to be added and if it is in the RF) # {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} @@ -769,7 +769,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - self.set_equipment_essential_to_run_event({''}) + # ap_oct23 - I believe this will almost always be mastectomy surgery with chemotherapy, so I think for equipment # we just need the standard surgery equipment list. We may need to add radiotherapy when more available. @@ -856,7 +856,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - self.set_equipment_essential_to_run_event({''}) + # ap_oct23 - Eva, I'm not aware of any equipment needed here. Clinical guidelines do not specify what # checks or monitoring are indicated @@ -916,7 +916,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - self.set_equipment_essential_to_run_event({''}) + # not sure there is any need for equipment here diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index 64a160c7d9..14936613b7 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1415,7 +1415,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "CardioMetabolicDisorders_Prevention_CommunityTestingForHypertension" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1468,7 +1468,7 @@ def __init__(self, module, person_id, conditions_to_investigate: List, has_any_c self.ACCEPTED_FACILITY_LEVEL = '1b' self.conditions_to_investigate = conditions_to_investigate self.has_any_cmd_symptom = has_any_cmd_symptom - self.set_equipment_essential_to_run_event({''}) + def do_for_each_condition(self, _c) -> bool: """What to do for each condition that will be investigated. Returns `bool` signalling whether a follow-up HSI @@ -1580,7 +1580,7 @@ def __init__(self, module, person_id, condition): self.TREATMENT_ID = 'CardioMetabolicDisorders_Prevention_WeightLoss' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + self.condition = condition @@ -1651,7 +1651,7 @@ def __init__(self, module, person_id, condition): self.TREATMENT_ID = 'CardioMetabolicDisorders_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + self.condition = condition @@ -1723,7 +1723,7 @@ def __init__(self, module, person_id, events_to_investigate: List): self.TREATMENT_ID = 'CardioMetabolicDisorders_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '2' - self.set_equipment_essential_to_run_event({''}) + self.events_to_investigate = events_to_investigate def do_for_each_event_to_be_investigated(self, _ev): diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 8e066de45d..78e54d1350 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1418,7 +1418,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AntenatalFirst': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1514,7 +1514,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1606,7 +1606,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1682,7 +1682,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1754,7 +1754,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1824,7 +1824,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1889,7 +1889,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1948,7 +1948,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'AntenatalCare_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2001,7 +2001,7 @@ def __init__(self, module, person_id, visit_number): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({('AntenatalFirst' if (self.visit_number == 1) else 'ANCSubsequent'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): @@ -2118,7 +2118,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2159,7 +2159,7 @@ def __init__(self, module, person_id): beddays = self.module.calculate_beddays(person_id) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': beddays}) - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2422,7 +2422,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.ALERT_OTHER_DISEASES = [] - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2472,7 +2472,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.ALERT_OTHER_DISEASES = [] - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2562,7 +2562,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1b' # any hospital? self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 3}) # todo: check with TC - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2650,7 +2650,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 5}) # todo: check with TC - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 0f538499cd..b54bca2134 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1109,7 +1109,7 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level - self.set_equipment_essential_to_run_event({''}) + @property def EXPECTED_APPT_FOOTPRINT(self): diff --git a/src/tlo/methods/copd.py b/src/tlo/methods/copd.py index 981bc62230..740b84806b 100644 --- a/src/tlo/methods/copd.py +++ b/src/tlo/methods/copd.py @@ -565,7 +565,7 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = self.all_facility_levels[self.facility_levels_index] self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 2}) - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """What to do when someone presents for care with an exacerbation. diff --git a/src/tlo/methods/depression.py b/src/tlo/methods/depression.py index 137d27cb1f..b29a67e380 100644 --- a/src/tlo/methods/depression.py +++ b/src/tlo/methods/depression.py @@ -519,7 +519,7 @@ def _check_for_suspected_depression( ): """ Returns True if any signs of depression are present, otherwise False. - + Raises an error if the treatment type cannot be identified. """ if treatment_id == "FirstAttendance_NonEmergency": @@ -574,7 +574,7 @@ def do_when_suspected_depression( """ This is called by any HSI event when depression is suspected or otherwise investigated. - At least one of the diagnosis_function or hsi_event arguments must be provided; if both + At least one of the diagnosis_function or hsi_event arguments must be provided; if both are provided, the hsi_event argument is ignored. - If the hsi_event argument is provided, that event is used to access the diagnosis manager and run diagnosis tests. @@ -870,7 +870,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MentOPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.num_of_sessions_had = 0 # A counter for the number of sessions of talking therapy had - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """Set the property `de_ever_talk_ther` to be True and schedule the next session in the course if the person @@ -903,7 +903,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Depression_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MentOPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -945,7 +945,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Depression_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/diarrhoea.py b/src/tlo/methods/diarrhoea.py index 6f5a28075e..610751bb1a 100644 --- a/src/tlo/methods/diarrhoea.py +++ b/src/tlo/methods/diarrhoea.py @@ -964,7 +964,7 @@ def do_at_generic_first_appt( self.rng.rand() < self.parameters["prob_hospitalization_on_danger_signs"] ) hsi_event_class = ( - HSI_Diarrhoea_Treatment_Inpatient if is_inpatient else + HSI_Diarrhoea_Treatment_Inpatient if is_inpatient else HSI_Diarrhoea_Treatment_Outpatient ) event = hsi_event_class(person_id=patient_id, module=self) @@ -1531,7 +1531,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Diarrhoea_Treatment_Outpatient' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """Run `do_treatment` for this person from an out-potient setting.""" @@ -1556,7 +1556,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 2}) - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """Run `do_treatment` for this person from an in-potient setting.""" diff --git a/src/tlo/methods/epi.py b/src/tlo/methods/epi.py index 8c60d8499c..2e9dde9283 100644 --- a/src/tlo/methods/epi.py +++ b/src/tlo/methods/epi.py @@ -395,7 +395,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi="1a", suppress_ self.TREATMENT_ID = self.treatment_id() self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi - self.set_equipment_essential_to_run_event({''}) # no equipment required + # no equipment required def treatment_id(self): """subclasses should implement this method to return the TREATMENT_ID""" diff --git a/src/tlo/methods/epilepsy.py b/src/tlo/methods/epilepsy.py index 5575c1f6f8..be43318ce5 100644 --- a/src/tlo/methods/epilepsy.py +++ b/src/tlo/methods/epilepsy.py @@ -601,7 +601,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Epilepsy_Treatment_Start' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -650,7 +650,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self._DEFAULT_APPT_FOOTPRINT self.ACCEPTED_FACILITY_LEVEL = '1b' self._counter_of_failed_attempts_due_to_unavailable_medicines = 0 - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index 410e5eaaa5..b20981c09b 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -2127,7 +2127,7 @@ def __init__( self.TREATMENT_ID = "Hiv_Test" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"VCTNegative": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """Do the testing and referring to other services""" @@ -2265,7 +2265,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MaleCirc": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' self.number_of_occurrences = 0 - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """ Do the circumcision for this man. If he is already circumcised, this is a follow-up appointment.""" @@ -2326,7 +2326,7 @@ def __init__(self, module, person_id, referred_from, repeat_visits): self.ACCEPTED_FACILITY_LEVEL = '1a' self.referred_from = referred_from self.repeat_visits = repeat_visits - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """ @@ -2395,7 +2395,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Hiv_Prevention_Prep" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"PharmDispensing": 1, "VCTNegative": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """Start PrEP for this person; or continue them on PrEP for 3 more months""" @@ -2457,7 +2457,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.counter_for_drugs_not_available = 0 self.counter_for_did_not_run = 0 - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """This is a Health System Interaction Event - start or continue HIV treatment for 6 more months""" diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 35b816a002..c4a136675e 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -2892,7 +2892,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'NormalDelivery': 1}) self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 2}) - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -3076,7 +3076,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'PostnatalCare_Maternal' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = self._get_facility_level_for_pnc(person_id) - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -3211,7 +3211,7 @@ def __init__(self, module, person_id, timing, facility_level_of_this_hsi): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.timing = timing - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -3349,7 +3349,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 5}) - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): logger.debug(key='message', data='HSI_Labour_PostnatalWardInpatientCare now running to capture ' diff --git a/src/tlo/methods/malaria.py b/src/tlo/methods/malaria.py index 847a653fa9..f2fe013d0a 100644 --- a/src/tlo/methods/malaria.py +++ b/src/tlo/methods/malaria.py @@ -948,7 +948,7 @@ def __init__(self, module, person_id, facility_level='1a'): 'Under5OPD' if person_age_years < 5 else 'Over5OPD': 1} ) self.ACCEPTED_FACILITY_LEVEL = '1a' if (self.facility_level == '1a') else '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): @@ -1046,7 +1046,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Malaria_Test' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ConWithDCSA': 1}) self.ACCEPTED_FACILITY_LEVEL = '0' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): @@ -1099,7 +1099,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): @@ -1193,7 +1193,7 @@ def __init__(self, module, person_id): ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): @@ -1253,7 +1253,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Malaria_Prevention_Iptp' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index 320b4331a3..8525e9c28c 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -430,7 +430,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): logger.debug(key="HSI_Measles_Treatment", diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 36d8467d56..05c6b57193 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -1330,7 +1330,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'PostnatalCare_Neonatal' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = self._get_facility_level_for_pnc(person_id) - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): nci = self.module.newborn_care_info @@ -1424,7 +1424,7 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index 2956c388ad..8468665b89 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -668,7 +668,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OesophagealCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + # I think this will need endoscope and biopsy needle. Also lab equipment needed to perform histology. # I can't see endoscope in equipment list but it may be given a slightly different name @@ -752,7 +752,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - self.set_equipment_essential_to_run_event({''}) + # equipment need here will be surgery @@ -829,7 +829,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OesophagealCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - self.set_equipment_essential_to_run_event({''}) + # equipment: I assume endoscope needed for this @@ -888,7 +888,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - self.set_equipment_essential_to_run_event({''}) + # when radiology available then palliative radiology may be performed but suggest we don't need to include yet # not sure what equipment needed for Endoscopic stent placement or Feeding tube which are done as palliative diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 2ed08d78ed..d270f6384c 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -658,7 +658,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OtherAdultCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + # equipment: investigations will differ by presenting symptom, but suggest we have biopsy and histology # and ultrasound @@ -742,7 +742,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - self.set_equipment_essential_to_run_event({''}) + # equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available @@ -816,7 +816,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OtherAdultCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - self.set_equipment_essential_to_run_event({''}) + # equipment: some checks will involve further biopsy, ultrasound, histology @@ -879,7 +879,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - self.set_equipment_essential_to_run_event({''}) + # equipment: in general not required I don't think def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 3749a7838f..08b86119fa 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -1265,7 +1265,7 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self.ALERT_OTHER_DISEASES = [] self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 4831dae512..55a3f16e7c 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -706,7 +706,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. @@ -758,7 +758,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. @@ -809,7 +809,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. @@ -890,7 +890,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - self.set_equipment_essential_to_run_event({''}) + # equipment as required for surgery @@ -963,7 +963,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - self.set_equipment_essential_to_run_event({''}) + # possibly biopsy and histology @@ -1022,7 +1022,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - self.set_equipment_essential_to_run_event({''}) + # generally not sure equipment is required as therapy is with drug, but can require castration surgery diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 7b9378ff1c..8bc88c43d6 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3198,7 +3198,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_Imaging' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'DiagRadio': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): self.sim.population.props.at[person_id, 'rt_diagnosed'] = True @@ -3272,7 +3272,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 8}) - self.set_equipment_essential_to_run_event({''}) + p = module.parameters # Load the parameters used in this event @@ -3823,7 +3823,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_ShockTreatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -3920,7 +3920,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_FractureCast' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): # Get the population and health system @@ -4060,7 +4060,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_OpenFractureTreatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MinorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -4179,7 +4179,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name @@ -4275,7 +4275,7 @@ def __init__(self, module, person_id): p = self.module.parameters self.prob_mild_burns = p['prob_mild_burns'] - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name @@ -4379,7 +4379,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_TetanusVaccine' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'EPI': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -4441,7 +4441,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({ ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -4744,7 +4744,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({}) - self.set_equipment_essential_to_run_event({''}) + p = self.module.parameters self.prob_perm_disability_with_treatment_severe_TBI = p['prob_perm_disability_with_treatment_severe_TBI'] @@ -5091,7 +5091,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Rti_MinorSurgeries' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MinorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/schisto.py b/src/tlo/methods/schisto.py index d261303d61..95994c098a 100644 --- a/src/tlo/methods/schisto.py +++ b/src/tlo/methods/schisto.py @@ -900,7 +900,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Schisto_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD' if under_5 else 'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + self._num_occurrences = 0 def apply(self, person_id, squeeze_factor): @@ -961,7 +961,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Schisto_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD' if under_5 else 'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """Do the treatment for this person.""" @@ -992,7 +992,7 @@ def __init__(self, module, person_id, beneficiaries_ids: Optional[Sequence] = No # `self.EXPECTED_APPT_FOOTPRINT` show that this requires 1 * that appointment type. self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """Provide the treatment to the beneficiaries of this HSI.""" diff --git a/src/tlo/methods/stunting.py b/src/tlo/methods/stunting.py index cef8f57c43..d2012a0cff 100644 --- a/src/tlo/methods/stunting.py +++ b/src/tlo/methods/stunting.py @@ -548,7 +548,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Undernutrition_Feeding' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'U5Malnutr': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index c228fc2c6a..36486b3842 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -1722,7 +1722,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Screening" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """Do the screening and referring to next tests""" @@ -1967,7 +1967,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Clinical" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Under5OPD": 0.5}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): """ Do the screening and referring process """ @@ -2035,7 +2035,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Xray" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"DiagRadio": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): @@ -2110,7 +2110,7 @@ def __init__(self, module, person_id, suppress_footprint=False): self.TREATMENT_ID = "Tb_Test_Xray" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"DiagRadio": 1}) self.ACCEPTED_FACILITY_LEVEL = '2' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): @@ -2182,7 +2182,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Treatment" self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + self.number_of_occurrences = 0 @property @@ -2331,7 +2331,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Test_FollowUp" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"TBFollowUp": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + def apply(self, person_id, squeeze_factor): p = self.module.parameters @@ -2469,7 +2469,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Prevention_Ipt" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.set_equipment_essential_to_run_event({''}) + self.number_of_occurrences = 0 def apply(self, person_id, squeeze_factor): From faf2fa8259807caf58073e1dcb166283be1d4276 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 11:54:14 +0100 Subject: [PATCH 429/443] tidy-up and tag todos --- src/tlo/methods/alri.py | 5 ++-- src/tlo/methods/bladder_cancer.py | 28 ++++++------------- src/tlo/methods/breast_cancer.py | 12 ++++---- src/tlo/methods/cardio_metabolic_disorders.py | 12 +++----- .../methods/care_of_women_during_pregnancy.py | 17 +---------- src/tlo/methods/contraception.py | 1 - src/tlo/methods/copd.py | 1 - src/tlo/methods/depression.py | 3 -- src/tlo/methods/diarrhoea.py | 2 -- src/tlo/methods/epi.py | 1 - src/tlo/methods/epilepsy.py | 4 +-- src/tlo/methods/hiv.py | 5 ---- src/tlo/methods/labour.py | 12 ++------ src/tlo/methods/malaria.py | 5 ---- src/tlo/methods/measles.py | 1 - src/tlo/methods/newborn_outcomes.py | 2 -- src/tlo/methods/oesophagealcancer.py | 16 ++++------- src/tlo/methods/other_adult_cancers.py | 14 +++------- src/tlo/methods/postnatal_supervisor.py | 1 - src/tlo/methods/prostate_cancer.py | 28 ++++++------------- src/tlo/methods/rti.py | 25 ++++------------- src/tlo/methods/schisto.py | 3 -- src/tlo/methods/stunting.py | 1 - src/tlo/methods/tb.py | 11 ++------ 24 files changed, 50 insertions(+), 160 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index 7e117997a9..74ff847f36 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2316,8 +2316,6 @@ def __init__(self, module: Module, person_id: int, facility_level: str = "0", in self._treatment_id_stub = 'Alri_Pneumonia_Treatment' self._facility_levels = ("0", "1a", "1b", "2") # Health facility levels at which care may be provided assert facility_level in self._facility_levels - - self.is_followup_following_treatment_failure = is_followup_following_treatment_failure if not inpatient: @@ -2770,7 +2768,7 @@ def _provide_bronchodilator_if_wheeze(self, facility_level, symptoms): if facility_level == '1a': _ = self._get_cons('Inhaled_Brochodilator') else: - # todo: determine if steroids here are IV (no consumables defined) + # todo: @Eva determine if steroids here are IV (no consumables defined) _ = self._get_cons('Brochodilator_and_Steroids') def do_on_follow_up_following_treatment_failure(self): @@ -2783,6 +2781,7 @@ def do_on_follow_up_following_treatment_failure(self): _ = self._get_cons('Ceftriaxone_therapy_for_severe_pneumonia') if _: + # todo @Eva -- intention here? self.add_equipment({'Infusion pump', 'Drip stand'}) def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 5ade59c714..729a79f035 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -2,7 +2,7 @@ Bladder Cancer Disease Module Limitations to note: -* Needs to represent the the DxTest 'cystoscopy_blood_urine_bladder_cancer' requires use of a cystoscope +* Needs to represent the DxTest 'cystoscopy_blood_urine_bladder_cancer' requires use of a cystoscope * Footprints of HSI -- pending input from expert on resources required. """ from __future__ import annotations @@ -693,9 +693,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - - - # equipment: (ultrsound guided) biopsy, lab equipment for histology + # todo @Eva equipment: (ultrsound guided) biopsy, lab equipment for histology def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -713,7 +711,7 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check consumables are available - # TODO: replace with cystoscope + # TODO: @Eva 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']) @@ -770,9 +768,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - - - # equipment: (ultrsound guided) biopsy, lab equipment for histology + # todo @Eva equipment: (ultrsound guided) biopsy, lab equipment for histology def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -790,7 +786,7 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check consumables are available - # TODO: replace with cystoscope + # TODO: @Eva 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']) @@ -852,9 +848,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - - - # equipment: standard equipment for surgery + # todo @Eva equipment: standard equipment for surgery def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -891,7 +885,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available and the treatment will go ahead - update the equipment - # TODO: link to surgical equipment package when that exists + # TODO: @Eva link to surgical equipment package when that exists self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -925,9 +919,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - - - # I assume ultrasound (Ultrasound scanning machine) and biopsy + # todo @Eva I assume ultrasound (Ultrasound scanning machine) and biopsy def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -984,9 +976,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - - - # no equipment as far as I am aware + # todo @Eva no equipment as far as I am aware def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 1c6b3b518b..040f9feaef 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -673,7 +673,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - + # todo Eva/Andrew - please resolve this # TODO: Eva's dummy equipment example (not sure if it actually needs to be added and if it is in the RF) # {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} @@ -770,7 +770,7 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - + # todo - @Eva/Andrew - please resolve # ap_oct23 - I believe this will almost always be mastectomy surgery with chemotherapy, so I think for equipment # we just need the standard surgery equipment list. We may need to add radiotherapy when more available. @@ -826,6 +826,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] + # todo - Eva - please resolve # TODO: Eva's dummy equipment example - it needs to be replaced by real items from the RF # Add used equipment # self.add_equipment({'Anything used for mastectomy'}) @@ -856,8 +857,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - - + # todo @Eva/Andrew - please resolve # ap_oct23 - Eva, I'm not aware of any equipment needed here. Clinical guidelines do not specify what # checks or monitoring are indicated @@ -916,9 +916,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - - - # not sure there is any need for equipment here + # todo @Eva - not sure there is any need for equipment here def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index 14936613b7..c88e31c1e5 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1416,7 +1416,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props person = df.loc[person_id] @@ -1469,7 +1468,6 @@ def __init__(self, module, person_id, conditions_to_investigate: List, has_any_c self.conditions_to_investigate = conditions_to_investigate self.has_any_cmd_symptom = has_any_cmd_symptom - def do_for_each_condition(self, _c) -> bool: """What to do for each condition that will be investigated. Returns `bool` signalling whether a follow-up HSI has been scheduled.""" @@ -1516,8 +1514,9 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Do test and trigger treatment (if necessary) for each condition: - if any(cond in self.conditions_to_investigate for cond in - ('diabetes', 'chronic_kidney_disease', 'chronic_ischemic_hd')): + 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] @@ -1581,7 +1580,6 @@ def __init__(self, module, person_id, condition): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.condition = condition def apply(self, person_id, squeeze_factor): @@ -1652,7 +1650,6 @@ def __init__(self, module, person_id, condition): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - self.condition = condition def apply(self, person_id, squeeze_factor): @@ -1723,7 +1720,6 @@ def __init__(self, module, person_id, events_to_investigate: List): self.TREATMENT_ID = 'CardioMetabolicDisorders_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '2' - self.events_to_investigate = events_to_investigate def do_for_each_event_to_be_investigated(self, _ev): @@ -1794,7 +1790,7 @@ def apply(self, person_id, squeeze_factor): data=('This is HSI_CardioMetabolicDisorders_SeeksEmergencyCareAndGetsTreatment: ' f'The squeeze-factor is {squeeze_factor}.'), ) - # TODO: link to surgical equipment package when that exists + # TODO: @Eva link to surgical equipment package when that exists self.add_equipment({'Analyser, Combined Chemistry and Electrolytes', 'Analyser, Haematology', 'Patient monitor', 'Drip stand', diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index 78e54d1350..b136adabff 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1419,7 +1419,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AntenatalFirst': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] @@ -1515,7 +1514,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] @@ -1607,7 +1605,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] @@ -1683,7 +1680,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] @@ -1755,7 +1751,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] @@ -1825,7 +1820,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] @@ -1890,7 +1884,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] @@ -1949,7 +1942,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ANCSubsequent': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2002,10 +1994,9 @@ def __init__(self, module, person_id, visit_number): else 'ANCSubsequent'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): - # TODO: n.b. equipment not added for this HSI but i think it will be deleted with the next PR + # TODO: @Eva - n.b. equipment not added for this HSI but i think it will be deleted with the next PR df = self.sim.population.props mother = df.loc[person_id] @@ -2119,7 +2110,6 @@ def __init__(self, module, person_id): self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2160,7 +2150,6 @@ def __init__(self, module, person_id): beddays = self.module.calculate_beddays(person_id) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': beddays}) - def apply(self, person_id, squeeze_factor): df = self.sim.population.props params = self.module.current_parameters @@ -2423,7 +2412,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1a' self.ALERT_OTHER_DISEASES = [] - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] @@ -2473,7 +2461,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1a' self.ALERT_OTHER_DISEASES = [] - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] @@ -2563,7 +2550,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' # any hospital? self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 3}) # todo: check with TC - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] @@ -2651,7 +2637,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 5}) # todo: check with TC - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index b54bca2134..92ec784bad 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1110,7 +1110,6 @@ def __init__(self, module, person_id, new_contraceptive): self.TREATMENT_ID = "Contraception_Routine" self.ACCEPTED_FACILITY_LEVEL = _facility_level - @property def EXPECTED_APPT_FOOTPRINT(self): """Return the expected appt footprint based on contraception method and whether the HSI has been rescheduled.""" diff --git a/src/tlo/methods/copd.py b/src/tlo/methods/copd.py index 740b84806b..8bd87348a3 100644 --- a/src/tlo/methods/copd.py +++ b/src/tlo/methods/copd.py @@ -566,7 +566,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 2}) - def apply(self, person_id, squeeze_factor): """What to do when someone presents for care with an exacerbation. * Provide treatment: whatever is available at this facility at this time (no referral). diff --git a/src/tlo/methods/depression.py b/src/tlo/methods/depression.py index b29a67e380..b7f6b4b379 100644 --- a/src/tlo/methods/depression.py +++ b/src/tlo/methods/depression.py @@ -871,7 +871,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self.num_of_sessions_had = 0 # A counter for the number of sessions of talking therapy had - def apply(self, person_id, squeeze_factor): """Set the property `de_ever_talk_ther` to be True and schedule the next session in the course if the person has not yet had 5 sessions.""" @@ -904,7 +903,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MentOPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -946,7 +944,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/diarrhoea.py b/src/tlo/methods/diarrhoea.py index 610751bb1a..3865710d9c 100644 --- a/src/tlo/methods/diarrhoea.py +++ b/src/tlo/methods/diarrhoea.py @@ -1532,7 +1532,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): """Run `do_treatment` for this person from an out-potient setting.""" @@ -1557,7 +1556,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1a' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 2}) - def apply(self, person_id, squeeze_factor): """Run `do_treatment` for this person from an in-potient setting.""" diff --git a/src/tlo/methods/epi.py b/src/tlo/methods/epi.py index 2e9dde9283..1cdf8a1612 100644 --- a/src/tlo/methods/epi.py +++ b/src/tlo/methods/epi.py @@ -395,7 +395,6 @@ def __init__(self, module, person_id, facility_level_of_this_hsi="1a", suppress_ self.TREATMENT_ID = self.treatment_id() self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi - # no equipment required def treatment_id(self): """subclasses should implement this method to return the TREATMENT_ID""" diff --git a/src/tlo/methods/epilepsy.py b/src/tlo/methods/epilepsy.py index be43318ce5..620c667188 100644 --- a/src/tlo/methods/epilepsy.py +++ b/src/tlo/methods/epilepsy.py @@ -602,7 +602,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -634,7 +633,7 @@ def apply(self, person_id, squeeze_factor): tclose=None, priority=0) - # todo: may need to consider iv diazepam as another hsi + # todo: @Eva - may need to consider iv diazepam as another hsi class HSI_Epilepsy_Follow_Up(HSI_Event, IndividualScopeEventMixin): @@ -651,7 +650,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self._counter_of_failed_attempts_due_to_unavailable_medicines = 0 - def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] diff --git a/src/tlo/methods/hiv.py b/src/tlo/methods/hiv.py index b20981c09b..575c94d6ff 100644 --- a/src/tlo/methods/hiv.py +++ b/src/tlo/methods/hiv.py @@ -2128,7 +2128,6 @@ def __init__( self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"VCTNegative": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): """Do the testing and referring to other services""" @@ -2266,7 +2265,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1a' self.number_of_occurrences = 0 - def apply(self, person_id, squeeze_factor): """ Do the circumcision for this man. If he is already circumcised, this is a follow-up appointment.""" self.number_of_occurrences += 1 # The current appointment is included in the count. @@ -2327,7 +2325,6 @@ def __init__(self, module, person_id, referred_from, repeat_visits): self.referred_from = referred_from self.repeat_visits = repeat_visits - def apply(self, person_id, squeeze_factor): """ Start infant prophylaxis for this infant lasting for duration of breastfeeding @@ -2396,7 +2393,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"PharmDispensing": 1, "VCTNegative": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): """Start PrEP for this person; or continue them on PrEP for 3 more months""" @@ -2458,7 +2454,6 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.counter_for_drugs_not_available = 0 self.counter_for_did_not_run = 0 - def apply(self, person_id, squeeze_factor): """This is a Health System Interaction Event - start or continue HIV treatment for 6 more months""" diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index c4a136675e..ebc8080a0a 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -696,7 +696,6 @@ def get_and_store_labour_item_codes(self): get_list_of_items = pregnancy_helper_functions.get_list_of_items # ---------------------------------- IV DRUG ADMIN CONSUMABLES ----------------------------------------------- - self.item_codes_lab_consumables['iv_drug_cons'] = \ get_list_of_items(self, ['Cannula iv (winged with injection pot) 18_each_CMST', 'Giving set iv administration + needle 15 drops/ml_each_CMST', @@ -2141,7 +2140,7 @@ def surgical_management_of_pph(self, hsi_event): else: # If the treatment is unsuccessful then women will require a hysterectomy to stop the bleeding - # Add used equipment + # todo @Eva Add used equipment # Todo: link to surgical equipment package when that exists hsi_event.add_equipment( {'Hysterectomy set'}) @@ -2893,7 +2892,6 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 2}) - def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info df = self.sim.population.props @@ -3077,7 +3075,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = self._get_facility_level_for_pnc(person_id) - def apply(self, person_id, squeeze_factor): mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info df = self.sim.population.props @@ -3212,7 +3209,6 @@ def __init__(self, module, person_id, timing, facility_level_of_this_hsi): self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.timing = timing - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mni = self.sim.modules['PregnancySupervisor'].mother_and_newborn_info @@ -3243,7 +3239,7 @@ def apply(self, person_id, squeeze_factor): elif (avail and sf_check) or (mni[person_id]['cs_indication'] == 'other'): # If intervention is delivered - add used equipment - # Todo: link to surgical equipment package when that exsists + # todo: @Eva link to surgical equipment package when that exsists self.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -3280,8 +3276,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'}) + self.add_equipment({'Hysterectomy set'}) df.at[person_id, 'la_has_had_hysterectomy'] = True # ============================= SURGICAL MANAGEMENT OF POSTPARTUM HAEMORRHAGE================================== @@ -3350,7 +3345,6 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'maternity_bed': 5}) - def apply(self, person_id, squeeze_factor): logger.debug(key='message', data='HSI_Labour_PostnatalWardInpatientCare now running to capture ' 'inpatient time for an unwell postnatal woman') diff --git a/src/tlo/methods/malaria.py b/src/tlo/methods/malaria.py index f2fe013d0a..60b36ec1b1 100644 --- a/src/tlo/methods/malaria.py +++ b/src/tlo/methods/malaria.py @@ -949,7 +949,6 @@ def __init__(self, module, person_id, facility_level='1a'): ) self.ACCEPTED_FACILITY_LEVEL = '1a' if (self.facility_level == '1a') else '1b' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1047,7 +1046,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'ConWithDCSA': 1}) self.ACCEPTED_FACILITY_LEVEL = '0' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1100,7 +1098,6 @@ def __init__(self, module, person_id): ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1194,7 +1191,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1254,7 +1250,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index 8525e9c28c..b7e817bced 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -431,7 +431,6 @@ def __init__(self, module, person_id): ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): logger.debug(key="HSI_Measles_Treatment", data=f"HSI_Measles_Treatment: treat person {person_id} for measles") diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 05c6b57193..78063484ce 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -1331,7 +1331,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = self._get_facility_level_for_pnc(person_id) - def apply(self, person_id, squeeze_factor): nci = self.module.newborn_care_info df = self.sim.population.props @@ -1425,7 +1424,6 @@ def __init__(self, module, person_id, facility_level_of_this_hsi): self.ACCEPTED_FACILITY_LEVEL = facility_level_of_this_hsi self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - def apply(self, person_id, squeeze_factor): logger.debug(key='message', data='HSI_PostnatalSupervisor_NeonatalWardInpatientCare now running to capture ' diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index 8468665b89..c6ffdf19c4 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -668,8 +668,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OesophagealCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - - + # todo @Eva # I think this will need endoscope and biopsy needle. Also lab equipment needed to perform histology. # I can't see endoscope in equipment list but it may be given a slightly different name @@ -689,7 +688,7 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check the consumables are available - # todo: replace with endoscope + # todo: @Eva replace with endoscope cons_avail = self.get_consumables(item_codes=self.module.item_codes_oesophageal_can['screening_biopsy_core'], optional_item_codes= self.module.item_codes_oesophageal_can['screening_biopsy_optional']) @@ -752,9 +751,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - - - # equipment need here will be surgery + # todo @Eva - equipment need here will be surgery def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -829,9 +826,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OesophagealCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - - - # equipment: I assume endoscope needed for this + # todo equipment: @Eva - I assume endoscope needed for this def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -888,8 +883,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - - + # todo @Eva # when radiology available then palliative radiology may be performed but suggest we don't need to include yet # not sure what equipment needed for Endoscopic stent placement or Feeding tube which are done as palliative # measures diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index d270f6384c..d6510b92f2 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -659,7 +659,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - + # todo @Eva # equipment: investigations will differ by presenting symptom, but suggest we have biopsy and histology # and ultrasound @@ -727,7 +727,6 @@ def apply(self, person_id, squeeze_factor): ) - class HSI_OtherAdultCancer_StartTreatment(HSI_Event, IndividualScopeEventMixin): """ This event is scheduled by HSI_OtherAdultCancer_Investigation_Following_other_adult_ca_symptom following a diagnosis @@ -742,9 +741,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - - - # equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available + # todo @Eva - equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -816,9 +813,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OtherAdultCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - - - # equipment: some checks will involve further biopsy, ultrasound, histology + # todo @Eva - equipment: some checks will involve further biopsy, ultrasound, histology def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -879,8 +874,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - - # equipment: in general not required I don't think + # todo @Eva equipment: in general not required I don't think def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 08b86119fa..25a2d54f6d 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -1266,7 +1266,6 @@ def __init__(self, module, person_id): self.ALERT_OTHER_DISEASES = [] self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - def apply(self, person_id, squeeze_factor): df = self.sim.population.props mother = df.loc[person_id] diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 55a3f16e7c..4e93b01a02 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -706,9 +706,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - - - # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. + # todo @Eva - biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -735,7 +733,7 @@ def apply(self, person_id, squeeze_factor): ) # Check consumable availability - # TODO: replace with PSA test when added to cons list + # TODO: @Eva 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: @@ -758,9 +756,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - - - # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. + # todo @Eva - biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -786,7 +782,7 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - # TODO: replace with PSA test when added to cons list + # TODO: @Eva - 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: @@ -809,9 +805,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - - - # biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. + # todo @Eva - biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -890,9 +884,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - - - # equipment as required for surgery + # todo: @Eva equipment as required for surgery def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -963,9 +955,7 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - - - # possibly biopsy and histology + # todo @Eva - possibly biopsy and histology def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1022,9 +1012,7 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - - - # generally not sure equipment is required as therapy is with drug, but can require castration surgery + # todo @Eva - generally not sure equipment is required as therapy is with drug, but can require castration surgery def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 8bc88c43d6..25f0b09a8e 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3199,15 +3199,14 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'DiagRadio': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - 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 'DiagRadio' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): - # TODO: use xray package when available - # TODO: robbie did not log the 'xray consumable' here as done in other modules (Tb) + # TODO: @Eva - use xray package when available + # TODO: @Eva - robbie did not log the 'xray consumable' here as done in other modules (Tb) self.add_equipment({'X-ray machine', 'X-ray viewer'}) elif 'Tomography' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): @@ -3273,7 +3272,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 8}) - p = module.parameters # Load the parameters used in this event self.prob_depressed_skull_fracture = p['prob_depressed_skull_fracture'] # proportion of depressed skull @@ -3516,7 +3514,7 @@ def apply(self, person_id, squeeze_factor): if df.loc[person_id, 'rt_ISS_score'] > self.hdu_cut_off_iss_score: - # TODO: some general ICU equipment listed below? additional would need to be added dependent on severity + # TODO: @Eva some general ICU equipment listed below? additional would need to be added dependent on severity # of illness self.add_equipment({ 'Patient monitor', 'Blood pressure machine', @@ -3824,7 +3822,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props # determine if this is a child @@ -3835,7 +3832,6 @@ def apply(self, person_id, squeeze_factor): if not df.at[person_id, 'is_alive']: return self.make_appt_footprint({}) get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name - # TODO: find a more complete list of required consumables for adults if is_child: self.module.item_codes_for_consumables_required['shock_treatment_child'] = { @@ -3863,9 +3859,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({}) @@ -3921,7 +3915,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'AccidentsandEmerg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - def apply(self, person_id, squeeze_factor): # Get the population and health system df = self.sim.population.props @@ -4061,7 +4054,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MinorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props if not df.at[person_id, 'is_alive']: @@ -4106,7 +4098,7 @@ def apply(self, person_id, squeeze_factor): data=f"Fracture casts available for person {person_id} {open_fracture_counts} open fractures" ) - # Todo: link to surgical equipment package when that exsists + # Todo: @Eva link to surgical equipment package when that exsists self.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -4180,7 +4172,6 @@ def __init__(self, module, person_id): ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - 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 @@ -4380,7 +4371,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'EPI': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props if not df.at[person_id, 'is_alive']: @@ -4442,7 +4432,6 @@ def __init__(self, module, person_id): ('Under5OPD' if self.sim.population.props.at[person_id, "age_years"] < 5 else 'Over5OPD'): 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props if not df.at[person_id, 'is_alive']: @@ -4745,7 +4734,6 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '1b' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({}) - p = self.module.parameters self.prob_perm_disability_with_treatment_severe_TBI = p['prob_perm_disability_with_treatment_severe_TBI'] self.allowed_interventions = p['allowed_interventions'] @@ -4834,7 +4822,7 @@ def apply(self, person_id, squeeze_factor): 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' - # TODO: not all surgeries conducted here are laparotomies however likely to have most appropriate equipment + # TODO: @eva - not all surgeries conducted here are laparotomies however likely to have most appropriate equipment # so should be fine for now self.add_equipment({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) @@ -5092,7 +5080,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MinorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props if not df.at[person_id, 'is_alive']: @@ -5154,7 +5141,7 @@ 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: - # TODO: link to surgical equipment package when that exists + # TODO: @Eva link to surgical equipment package when that exists self.add_equipment({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) # create a dictionary to store the recovery times for each injury in days diff --git a/src/tlo/methods/schisto.py b/src/tlo/methods/schisto.py index 95994c098a..d1bfac8a0b 100644 --- a/src/tlo/methods/schisto.py +++ b/src/tlo/methods/schisto.py @@ -900,7 +900,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = 'Schisto_Treatment' self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD' if under_5 else 'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self._num_occurrences = 0 def apply(self, person_id, squeeze_factor): @@ -962,7 +961,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'Under5OPD' if under_5 else 'Over5OPD': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): """Do the treatment for this person.""" if self.get_consumables(item_codes=self.module.item_code_for_praziquantel): @@ -993,7 +991,6 @@ def __init__(self, module, person_id, beneficiaries_ids: Optional[Sequence] = No self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): """Provide the treatment to the beneficiaries of this HSI.""" diff --git a/src/tlo/methods/stunting.py b/src/tlo/methods/stunting.py index d2012a0cff..bacb9f4d7d 100644 --- a/src/tlo/methods/stunting.py +++ b/src/tlo/methods/stunting.py @@ -549,7 +549,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'U5Malnutr': 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 36486b3842..3ca1a0ae6a 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -1723,7 +1723,6 @@ def __init__(self, module, person_id, suppress_footprint=False): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): """Do the screening and referring to next tests""" @@ -1968,7 +1967,6 @@ def __init__(self, module, person_id, suppress_footprint=False): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Under5OPD": 0.5}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): """ Do the screening and referring process """ @@ -2036,7 +2034,6 @@ def __init__(self, module, person_id, suppress_footprint=False): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"DiagRadio": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2059,7 +2056,7 @@ def apply(self, person_id, squeeze_factor): ) if test_result is not None: # Add used equipment - self.add_equipment({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items + self.add_equipment({'X-ray machine', 'X-ray viewer'}) # TODO: @Eva make an x-ray pkg with these items # if consumables not available, refer to level 2 # return blank footprint as xray did not occur @@ -2111,7 +2108,6 @@ def __init__(self, module, person_id, suppress_footprint=False): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"DiagRadio": 1}) self.ACCEPTED_FACILITY_LEVEL = '2' - def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -2134,7 +2130,7 @@ def apply(self, person_id, squeeze_factor): ) if test_result is not None: # Add used equipment - self.add_equipment({'X-ray machine', 'X-ray viewer'}) # TODO: make an x-ray pkg with these items + self.add_equipment({'X-ray machine', 'X-ray viewer'}) # TODO: Eva make an x-ray pkg with these items # if consumables not available, rely on clinical diagnosis # return blank footprint as xray was not available @@ -2182,7 +2178,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Treatment" self.ACCEPTED_FACILITY_LEVEL = '1a' - self.number_of_occurrences = 0 @property @@ -2332,7 +2327,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"TBFollowUp": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - def apply(self, person_id, squeeze_factor): p = self.module.parameters df = self.sim.population.props @@ -2469,7 +2463,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "Tb_Prevention_Ipt" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1a' - self.number_of_occurrences = 0 def apply(self, person_id, squeeze_factor): From 3be6e7001805ac6892b4dc05972b6b1dfe287259 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 12:23:07 +0100 Subject: [PATCH 430/443] tidy-up cancer_consumables.py --- src/tlo/methods/bladder_cancer.py | 5 ++-- src/tlo/methods/breast_cancer.py | 5 ++-- src/tlo/methods/cancer_consumables.py | 37 +++++++++++++++----------- src/tlo/methods/oesophagealcancer.py | 5 ++-- src/tlo/methods/other_adult_cancers.py | 5 ++-- src/tlo/methods/prostate_cancer.py | 5 ++-- 6 files changed, 37 insertions(+), 25 deletions(-) diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 729a79f035..a69d27b9a2 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -16,7 +16,8 @@ from tlo.core import IndividualPropertyUpdates from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata, cancer_consumables +from tlo.methods import Metadata +from tlo.methods.cancer_consumables import get_consumable_item_codes_cancers from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest @@ -377,7 +378,7 @@ def initialise_simulation(self, sim): """ # We call the following function to store the required consumables for the simulation run within the appropriate # dictionary - cancer_consumables.get_consumable_item_codes_cancers(self, self.item_codes_bladder_can) + self.item_codes_bladder_can = get_consumable_item_codes_cancers(self) # ----- SCHEDULE LOGGING EVENTS ----- # Schedule logging event to happen immediately diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 040f9feaef..596dd68a3f 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -15,7 +15,8 @@ from tlo.core import IndividualPropertyUpdates from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata, cancer_consumables +from tlo.methods import Metadata +from tlo.methods.cancer_consumables import get_consumable_item_codes_cancers from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest @@ -349,7 +350,7 @@ def initialise_simulation(self, sim): """ # We call the following function to store the required consumables for the simulation run within the appropriate # dictionary - cancer_consumables.get_consumable_item_codes_cancers(self, self.item_codes_breast_can) + self.item_codes_breast_can = get_consumable_item_codes_cancers(self) # ----- SCHEDULE LOGGING EVENTS ----- # Schedule logging event to happen immediately diff --git a/src/tlo/methods/cancer_consumables.py b/src/tlo/methods/cancer_consumables.py index 0abe4ed04a..9c90273ac9 100644 --- a/src/tlo/methods/cancer_consumables.py +++ b/src/tlo/methods/cancer_consumables.py @@ -1,20 +1,25 @@ """ This file stores defines the consumables required within the cancer modules """ +from typing import Dict +from tlo import Module -def get_consumable_item_codes_cancers(self, cons_dict): + +def get_consumable_item_codes_cancers(cancer_module: Module) -> Dict[str, int]: """ - This function stores the relevant item codes for cancer consumables across the five cancer modules to prevent + Returns dict the relevant item_codes for the consumables across the five cancer modules. This is intended to prevent repetition within module code. """ def get_list_of_items(item_list): - item_code_function = self.sim.modules['HealthSystem'].get_item_code_from_item_name - codes = [item_code_function(item) for item in item_list] - return codes + item_lookup_fn = cancer_module.sim.modules['HealthSystem'].get_item_code_from_item_name + return list(map(item_lookup_fn, item_list)) + + cons_dict = dict() - # to do: add syringes, dressing + # Add items that are needed for all cancer modules + # todo: @Eva - add syringes, dressing cons_dict['screening_biopsy_core'] = get_list_of_items(['Biopsy needle']) cons_dict['screening_biopsy_optional'] = \ @@ -35,10 +40,10 @@ def get_list_of_items(item_list): 'Gauze, absorbent 90cm x 40m_each_CMST', 'Cannula iv (winged with injection pot) 18_each_CMST']) - # This is not an exhaustive list of drugs required for palliation cons_dict['palliation'] = \ get_list_of_items(['morphine sulphate 10 mg/ml, 1 ml, injection (nt)_10_IDA', 'Diazepam, injection, 5 mg/ml, in 2 ml ampoule', + # N.B. This is not an exhaustive list of drugs required for palliation ]) cons_dict['iv_drug_cons'] = \ @@ -47,18 +52,19 @@ def get_list_of_items(item_list): 'Disposables gloves, powder free, 100 pieces per box' ]) - if ('BreastCancer' in self.sim.modules) and (self == self.sim.modules['BreastCancer']): + # Add items that are specific to each cancer module + if 'BreastCancer' == cancer_module.name: - # TODO: chemotharpy protocols??: TAC(Taxotere, Adriamycin, and Cyclophosphamide), AC (anthracycline and + # TODO: @Eva chemotharpy protocols??: TAC(Taxotere, Adriamycin, and Cyclophosphamide), AC (anthracycline and # cyclophosphamide) +/-Taxane, TC (Taxotere and cyclophosphamide), CMF (cyclophosphamide, methotrexate, # and fluorouracil), FEC-75 (5-Fluorouracil, Epirubicin, Cyclophosphamide). HER 2 +: Add Trastuzumab # only chemotherapy i consumable list which is also in suggested protocol is cyclo cons_dict['treatment_chemotherapy'] = get_list_of_items(['Cyclophosphamide, 1 g']) - elif ('ProstateCancer' in self.sim.modules) and (self == self.sim.modules['ProstateCancer']): + elif 'ProstateCancer' == cancer_module.name: - # TODO: Prostate specific antigen test is listed in ResourceFile_Consumables_availability_and_usage but not + # TODO: @Eva Prostate specific antigen test is listed in ResourceFile_Consumables_availability_and_usage but not # ResourceFile_Consumables_Items_and_Package # cons_dict['screening_psa_test_core'] = get_list_of_items(['Prostate specific antigen test']) @@ -66,18 +72,18 @@ def get_list_of_items(item_list): get_list_of_items(['Blood collecting tube, 5 ml', 'Disposables gloves, powder free, 100 pieces per box']) - elif ('BladderCancer' in self.sim.modules) and (self == self.sim.modules['BladderCancer']): + elif 'BladderCancer' == cancer_module.name: # Note: bladder cancer is not in the malawi STG 2023 therefore no details on chemotherapy - # TODO: cytoscope is listed in ResourceFile_Consumables_availability_and_usage but not + # TODO: @Eva cytoscope is listed in ResourceFile_Consumables_availability_and_usage but not # ResourceFile_Consumables_Items_and_Packages # cons_dict['screening_cystoscopy_core'] = get_list_of_items(['Cytoscope']) cons_dict['screening_cystoscope_optional'] = get_list_of_items(['Specimen container']) - elif ('OesophagealCancer' in self.sim.modules) and (self == self.sim.modules['OesophagealCancer']): + elif 'OesophagealCancer' == cancer_module.name: - # TODO: endoscope is listed in ResourceFile_Consumables_availability_and_usage but not + # TODO: @Eva endoscope is listed in ResourceFile_Consumables_availability_and_usage but not # ResourceFile_Consumables_Items_and_Packages # cons_dict['screening_endoscope_core'] = get_list_of_items(['Endoscope']) @@ -87,3 +93,4 @@ def get_list_of_items(item_list): cons_dict['treatment_chemotherapy'] = get_list_of_items(['Cisplatin 50mg Injection']) + return cons_dict diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index c6ffdf19c4..c4990b2645 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -17,7 +17,8 @@ from tlo.core import IndividualPropertyUpdates from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata, cancer_consumables +from tlo.methods import Metadata +from tlo.methods.cancer_consumables import get_consumable_item_codes_cancers from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest @@ -364,7 +365,7 @@ def initialise_simulation(self, sim): """ # We call the following function to store the required consumables for the simulation run within the appropriate # dictionary - cancer_consumables.get_consumable_item_codes_cancers(self, self.item_codes_oesophageal_can) + self.item_codes_oesophageal_can = get_consumable_item_codes_cancers(self) # ----- SCHEDULE LOGGING EVENTS ----- # Schedule logging event to happen immediately diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index d6510b92f2..8f87c74505 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -15,7 +15,8 @@ from tlo.core import IndividualPropertyUpdates from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata, cancer_consumables +from tlo.methods import Metadata +from tlo.methods.cancer_consumables import get_consumable_item_codes_cancers from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest @@ -372,7 +373,7 @@ def initialise_simulation(self, sim): """ # We call the following function to store the required consumables for the simulation run within the appropriate # dictionary - cancer_consumables.get_consumable_item_codes_cancers(self, self.item_codes_other_can) + self.item_codes_other_can = get_consumable_item_codes_cancers(self) # ----- SCHEDULE LOGGING EVENTS ----- # Schedule logging event to happen immediately diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 4e93b01a02..da3d49d3ed 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -15,7 +15,8 @@ from tlo.core import IndividualPropertyUpdates from tlo.events import IndividualScopeEventMixin, PopulationScopeEventMixin, RegularEvent from tlo.lm import LinearModel, LinearModelType, Predictor -from tlo.methods import Metadata, cancer_consumables +from tlo.methods import Metadata +from tlo.methods.cancer_consumables import get_consumable_item_codes_cancers from tlo.methods.causes import Cause from tlo.methods.demography import InstantaneousDeath from tlo.methods.dxmanager import DxTest @@ -377,7 +378,7 @@ def initialise_simulation(self, sim): """ # We call the following function to store the required consumables for the simulation run within the appropriate # dictionary - cancer_consumables.get_consumable_item_codes_cancers(self, self.item_codes_prostate_can) + self.item_codes_prostate_can = get_consumable_item_codes_cancers(self) # ----- SCHEDULE LOGGING EVENTS ----- # Schedule logging event to happen immediately From 8bcda48f9426b4908cd1c851a2009c9b9b6a1ce3 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 15:18:31 +0100 Subject: [PATCH 431/443] temporarily make simulation shorter, smaller and with fewer draws to create a test run. --- .../calibration_analyses/scenarios/long_run_all_diseases.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py index 4a354e026c..f0a202fce0 100644 --- a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py +++ b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py @@ -21,10 +21,10 @@ def __init__(self): super().__init__() self.seed = 0 self.start_date = Date(2010, 1, 1) - self.end_date = Date(2031, 1, 1) # The simulation will stop before reaching this date. - self.pop_size = 20_000 + self.end_date = Date(2012, 1, 1) # The simulation will stop before reaching this date. + self.pop_size = 5_000 self.number_of_draws = 1 - self.runs_per_draw = 10 + self.runs_per_draw = 2 def log_configuration(self): return { From 0dd00c439b4122d475aba0c6cf337f47fe732989 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Mon, 13 May 2024 15:19:24 +0100 Subject: [PATCH 432/443] Revert "temporarily make simulation shorter, smaller and with fewer draws to create a test run." This reverts commit 8bcda48f9426b4908cd1c851a2009c9b9b6a1ce3. --- .../calibration_analyses/scenarios/long_run_all_diseases.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py index f0a202fce0..4a354e026c 100644 --- a/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py +++ b/src/scripts/calibration_analyses/scenarios/long_run_all_diseases.py @@ -21,10 +21,10 @@ def __init__(self): super().__init__() self.seed = 0 self.start_date = Date(2010, 1, 1) - self.end_date = Date(2012, 1, 1) # The simulation will stop before reaching this date. - self.pop_size = 5_000 + self.end_date = Date(2031, 1, 1) # The simulation will stop before reaching this date. + self.pop_size = 20_000 self.number_of_draws = 1 - self.runs_per_draw = 2 + self.runs_per_draw = 10 def log_configuration(self): return { From d1c869982de7f7fe675fc141cebe702bfa6e880f Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Thu, 16 May 2024 08:31:38 +0100 Subject: [PATCH 433/443] roll back `codes_to_item_list.py` --- .../codes_to_items_list.py | 75 ------------------- 1 file changed, 75 deletions(-) delete mode 100644 src/scripts/data_file_processing/codes_to_items_list.py diff --git a/src/scripts/data_file_processing/codes_to_items_list.py b/src/scripts/data_file_processing/codes_to_items_list.py deleted file mode 100644 index da169a06e5..0000000000 --- a/src/scripts/data_file_processing/codes_to_items_list.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -(1) Can be used for a list of items without item codes yet saved in a csv file named 'csv_file_to_update_name'. - -This script will assign unique code to each unique item name which has no code assigned yet. The codes are -assigned in order from the sequence 0, 1, 2, .... - -Duplicated items are allowed, the same code will be assigned to the same items. - -(2) Can be used when new items are added later without item codes but some items with codes are already in the list. - -This script will keep the existing codes for items with already assigned code and for items without existing -code will assign new code (continue in sequence, i.e. if the highest code is 5, it assigns new codes from the continuing -sequence 6, 7, 8, ...). - ------- -NB. Make sure the 'csv_file_to_update_name' is the file you want to update. The output will be named -'csv_file_to_update_name' + '_new.csv' to avoid unintentionally losing the previous version. ------- -""" - -from pathlib import Path - -import pandas as pd - -# Get the path of the current script file -script_path = Path(__file__) -print(script_path) - -# ############################# -# ## CHANGE THIS FOR YOUR FILE -# Specify name of the csv file -csv_file_to_update_name = 'ResourceFile_Equipment_withoutEquipmentCodes' -# Specify the file path to csv file -file_path = script_path.parent.parent.parent.parent / 'resources/healthsystem/infrastructure_and_equipment' -# Specify the names of columns containing the item names and item codes -item_col_name = 'Equip_Item' -code_col_name = 'Equip_Code' -# ############################# - -# Load the CSV RF into a DataFrame -df = pd.read_csv(Path(file_path) / str(csv_file_to_update_name + '.csv')) - -# Find unique values in Equipment that have no code and are not None or empty -unique_values =\ - df.loc[df[code_col_name].isna() & df[item_col_name].notna() & (df[item_col_name] != ''), item_col_name].unique() - -# Create a mapping of unique values to codes -value_to_code = {} -# Initialize the starting code value -if not df[code_col_name].isna().all(): - next_code = int(df[code_col_name].max()) + 1 -else: - next_code = 0 - -# Iterate through unique values -for value in unique_values: - # Check if there is at least one existing code for this value - matching_rows = df.loc[df[item_col_name] == value, code_col_name].dropna() - if not matching_rows.empty: - # Use the existing code for this value - existing_code = int(matching_rows.iloc[0]) - # TODO: verify all the codes are the same - else: - # If no existing codes, start with the next available code - existing_code = next_code - next_code += 1 - value_to_code[value] = existing_code - # Update the code_col_name column for matching rows - df.loc[df[item_col_name] == value, code_col_name] = existing_code - -# Convert code_col_name column to integers -df[code_col_name] = df[code_col_name].astype('Int64') # Convert to nullable integer type - -# Save CSV with equipment codes -df.to_csv(Path(file_path) / str(csv_file_to_update_name + '_new.csv'), index=False) From f566709060dd6b7bdad89f46cf8aef5a4d83f374 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Fri, 24 May 2024 16:15:20 +0100 Subject: [PATCH 434/443] todo updates and minor fixes --- src/tlo/methods/alri.py | 9 +++--- src/tlo/methods/bladder_cancer.py | 15 ++++------ src/tlo/methods/breast_cancer.py | 29 +------------------ src/tlo/methods/cardio_metabolic_disorders.py | 2 +- .../methods/care_of_women_during_pregnancy.py | 2 -- src/tlo/methods/contraception.py | 2 +- src/tlo/methods/diarrhoea.py | 1 - src/tlo/methods/epilepsy.py | 2 -- src/tlo/methods/labour.py | 7 ++--- src/tlo/methods/measles.py | 1 - src/tlo/methods/newborn_outcomes.py | 2 -- src/tlo/methods/oesophagealcancer.py | 13 ++------- src/tlo/methods/other_adult_cancers.py | 9 +----- src/tlo/methods/postnatal_supervisor.py | 2 ++ src/tlo/methods/prostate_cancer.py | 11 ++----- src/tlo/methods/rti.py | 22 +++++++------- src/tlo/methods/tb.py | 6 ++-- 17 files changed, 37 insertions(+), 98 deletions(-) diff --git a/src/tlo/methods/alri.py b/src/tlo/methods/alri.py index 74ff847f36..dcf7b6bb54 100644 --- a/src/tlo/methods/alri.py +++ b/src/tlo/methods/alri.py @@ -2768,7 +2768,7 @@ def _provide_bronchodilator_if_wheeze(self, facility_level, symptoms): if facility_level == '1a': _ = self._get_cons('Inhaled_Brochodilator') else: - # todo: @Eva determine if steroids here are IV (no consumables defined) + # n.b. this is never called, see issue 1172 _ = self._get_cons('Brochodilator_and_Steroids') def do_on_follow_up_following_treatment_failure(self): @@ -2776,12 +2776,11 @@ 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 _: - # todo @Eva -- intention here? + if cons_avail: self.add_equipment({'Infusion pump', 'Drip stand'}) def apply(self, person_id, squeeze_factor): diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index a69d27b9a2..60f4684ddc 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -694,7 +694,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - # todo @Eva equipment: (ultrsound guided) biopsy, lab equipment for histology def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -712,7 +711,7 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check consumables are available - # TODO: @Eva replace with cystoscope + # TODO: DISCUSSED - @Eva 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']) @@ -720,7 +719,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # Use a biopsy to diagnose whether the person has bladder Cancer # If consumables are available update the use of equipment and run the dx_test representing the biopsy - self.add_equipment({'Cystoscope', 'Ordinary Microscope'}) + 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( @@ -769,7 +768,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - # todo @Eva equipment: (ultrsound guided) biopsy, lab equipment for histology def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -787,7 +785,7 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check consumables are available - # TODO: @Eva replace with cystoscope + # TODO: DISCUSSED - @Eva 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']) @@ -795,7 +793,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # Use a biopsy to diagnose whether the person has bladder Cancer # If consumables are available log the use of equipment and run the dx_test representing the biopsy - self.add_equipment({'Cystoscope', 'Ordinary Microscope'}) + 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( @@ -849,7 +847,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({'MajorSurg': 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 5}) - # todo @Eva equipment: standard equipment for surgery def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -886,7 +883,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available and the treatment will go ahead - update the equipment - # TODO: @Eva link to surgical equipment package when that exists + # TODO: DISCUSSED - replace with package and call here self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -920,7 +917,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BladderCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - # todo @Eva I assume ultrasound (Ultrasound scanning machine) and biopsy def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -977,7 +973,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - # todo @Eva no equipment as far as I am aware def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 596dd68a3f..08f47a589b 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -674,18 +674,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1, "Mammography": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' # Biopsy only available at level 3 and above. - # todo Eva/Andrew - please resolve this - # TODO: Eva's dummy equipment example (not sure if it actually needs to be added and if it is in the RF) - # {'Slice Master sample processing Unit', 'Paraffin Dispense', 'Whatever used with biopsy'} - - # ap_oct23 - Eva, I cannot locate a biopsy needle in the equipment - perhaps it is in consumables - # the other equipment need is for histology in the lab - there is a whole long list of items needed - # for histology done on the sample in the lab - do we need to add each of these, or can we have a - # package ? I do not think mammography is done at this point but I could be wrong. - - # biopsy always performed with this HSI, hence always used the same set of equipment - # TODO: but the appt footprints suggests mammography to be provided - def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -771,16 +759,10 @@ def __init__(self, module, person_id): self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - # todo - @Eva/Andrew - please resolve - # ap_oct23 - I believe this will almost always be mastectomy surgery with chemotherapy, so I think for equipment - # we just need the standard surgery equipment list. We may need to add radiotherapy when more available. - 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() @@ -814,7 +796,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - add the used equipment - # TODO: link to surgical equipment package when that exists + # TODO: DISCUSSED - link to new surgical pacakge self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -827,11 +809,6 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, "brc_date_treatment"] = self.sim.date df.at[person_id, "brc_stage_at_which_treatment_given"] = df.at[person_id, "brc_status"] - # todo - Eva - please resolve - # TODO: Eva's dummy equipment example - it needs to be replaced by real items from the RF - # Add used equipment - # self.add_equipment({'Anything used for mastectomy'}) - # Schedule a post-treatment check for 12 months: hs.schedule_hsi_event( hsi_event=HSI_BreastCancer_PostTreatmentCheck( @@ -858,9 +835,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "BreastCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - # todo @Eva/Andrew - please resolve - # ap_oct23 - Eva, I'm not aware of any equipment needed here. Clinical guidelines do not specify what - # checks or monitoring are indicated def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -917,7 +891,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - # todo @Eva - not sure there is any need for equipment here def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index c88e31c1e5..d30512dd13 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1790,7 +1790,7 @@ def apply(self, person_id, squeeze_factor): data=('This is HSI_CardioMetabolicDisorders_SeeksEmergencyCareAndGetsTreatment: ' f'The squeeze-factor is {squeeze_factor}.'), ) - # TODO: @Eva link to surgical equipment package when that exists + # to do @eva/joe - DISCUSSED, make ICU package and use here self.add_equipment({'Analyser, Combined Chemistry and Electrolytes', 'Analyser, Haematology', 'Patient monitor', 'Drip stand', diff --git a/src/tlo/methods/care_of_women_during_pregnancy.py b/src/tlo/methods/care_of_women_during_pregnancy.py index b136adabff..cfbf07e4d0 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1067,7 +1067,6 @@ def gdm_screening(self, hsi_event): # she is referred for treatment if avail: - # TODO: this actually might be a fasting blood glucose test (not using glucometer) hsi_event.add_equipment({'Glucometer'}) if self.sim.modules['HealthSystem'].dx_manager.run_dx_test( @@ -1996,7 +1995,6 @@ def __init__(self, module, person_id, visit_number): def apply(self, person_id, squeeze_factor): - # TODO: @Eva - n.b. equipment not added for this HSI but i think it will be deleted with the next PR df = self.sim.population.props mother = df.loc[person_id] diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 92ec784bad..06554564da 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1206,7 +1206,7 @@ def apply(self, person_id, squeeze_factor): self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' }) - # TODO: add equip for minor surgery + # TO DO: @Eva - DISCUSSED add equip for minor surgery elif _new_contraceptive == 'IUD': self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Sponge Holding Forceps' diff --git a/src/tlo/methods/diarrhoea.py b/src/tlo/methods/diarrhoea.py index 3865710d9c..97f5b4bceb 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 diff --git a/src/tlo/methods/epilepsy.py b/src/tlo/methods/epilepsy.py index 620c667188..6c4ff0e41d 100644 --- a/src/tlo/methods/epilepsy.py +++ b/src/tlo/methods/epilepsy.py @@ -633,8 +633,6 @@ def apply(self, person_id, squeeze_factor): tclose=None, priority=0) - # todo: @Eva - may need to consider iv diazepam as another hsi - class HSI_Epilepsy_Follow_Up(HSI_Event, IndividualScopeEventMixin): diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index ebc8080a0a..9458a4eb03 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -2128,7 +2128,7 @@ def surgical_management_of_pph(self, hsi_event): if avail and sf_check: # Add used equipment - # Todo: link to surgical equipment package when that exsists + # Todo: DISCUSSED - @eva surgical package hsi_event.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -2139,9 +2139,6 @@ def surgical_management_of_pph(self, hsi_event): self.pph_treatment.set(person_id, 'surgery') else: # If the treatment is unsuccessful then women will require a hysterectomy to stop the bleeding - - # todo @Eva Add used equipment - # Todo: link to surgical equipment package when that exists hsi_event.add_equipment( {'Hysterectomy set'}) self.pph_treatment.set(person_id, 'hysterectomy') @@ -3239,7 +3236,7 @@ def apply(self, person_id, squeeze_factor): elif (avail and sf_check) or (mni[person_id]['cs_indication'] == 'other'): # If intervention is delivered - add used equipment - # todo: @Eva link to surgical equipment package when that exsists + # todo: DISCUSSED @Eva link to surgical equipment package when that exsists self.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) diff --git a/src/tlo/methods/measles.py b/src/tlo/methods/measles.py index b7e817bced..f58cc5f662 100644 --- a/src/tlo/methods/measles.py +++ b/src/tlo/methods/measles.py @@ -455,7 +455,6 @@ def apply(self, person_id, squeeze_factor): data=f"HSI_Measles_Treatment: giving required measles treatment to person {person_id}") if "respiratory_symptoms" in symptoms: - # Add used equipment self.add_equipment({'Oxygen concentrator', 'Oxygen cylinder, with regulator'}) # modify person property which is checked when scheduled death occurs (or shouldn't occur) diff --git a/src/tlo/methods/newborn_outcomes.py b/src/tlo/methods/newborn_outcomes.py index 78063484ce..2c4e35667a 100644 --- a/src/tlo/methods/newborn_outcomes.py +++ b/src/tlo/methods/newborn_outcomes.py @@ -977,7 +977,6 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): df.at[person_id, 'nb_supp_care_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_supportive_care', hsi_event) - # Add used equipment hsi_event.add_equipment({'Drip stand', 'Infusion pump'}) # The same pattern is then followed for health centre care @@ -990,7 +989,6 @@ def assessment_and_treatment_newborn_sepsis(self, hsi_event, facility_type): df.at[person_id, 'nb_inj_abx_neonatal_sepsis'] = True pregnancy_helper_functions.log_met_need(self, 'neo_sep_abx', hsi_event) - # Add used equipment 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 c4990b2645..b22d1485ab 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -669,9 +669,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OesophagealCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - # todo @Eva - # I think this will need endoscope and biopsy needle. Also lab equipment needed to perform histology. - # I can't see endoscope in equipment list but it may be given a slightly different name def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -689,7 +686,7 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check the consumables are available - # todo: @Eva replace with endoscope + # todo: DISCUSS - add endoscope consumable to screening biopsy cons_avail = self.get_consumables(item_codes=self.module.item_codes_oesophageal_can['screening_biopsy_core'], optional_item_codes= self.module.item_codes_oesophageal_can['screening_biopsy_optional']) @@ -752,7 +749,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - # todo @Eva - equipment need here will be surgery def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -788,7 +784,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available and the treatment will go ahead - update the equipment - # TODO: link to surgical equipment package when that exists + # TODO: DISCUSSED - add surgery package self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -827,7 +823,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OesophagealCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - # todo equipment: @Eva - I assume endoscope needed for this def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -884,10 +879,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - # todo @Eva - # when radiology available then palliative radiology may be performed but suggest we don't need to include yet - # not sure what equipment needed for Endoscopic stent placement or Feeding tube which are done as palliative - # measures def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 8f87c74505..f8dd619a5d 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -660,10 +660,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - # todo @Eva - # equipment: investigations will differ by presenting symptom, but suggest we have biopsy and histology - # and ultrasound - def apply(self, person_id, squeeze_factor): df = self.sim.population.props hs = self.sim.modules["HealthSystem"] @@ -742,7 +738,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - # todo @Eva - equipment: a proportion of these cancers will require surgery - also radiotherapy in some cases when available def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -777,7 +772,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment - # TODO: link to surgical equipment package when that exists + # TODO: DISCUSSED - link to surgical equipment package when that exists self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -814,7 +809,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "OtherAdultCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - # todo @Eva - equipment: some checks will involve further biopsy, ultrasound, histology def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -875,7 +869,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - # todo @Eva equipment: in general not required I don't think def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 25a2d54f6d..23c9949b01 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -1292,6 +1292,8 @@ def apply(self, person_id, squeeze_factor): self.get_consumables(item_codes=of_repair_cons) + # TODO DISCUSSED @Joe - add surgical package + # 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 da3d49d3ed..fceea05b4a 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -707,7 +707,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - # todo @Eva - biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -734,7 +733,7 @@ def apply(self, person_id, squeeze_factor): ) # Check consumable availability - # TODO: @Eva replace with PSA test when added to cons list + # TODO: DISCUSSED - but to remain as 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: @@ -757,7 +756,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - # todo @Eva - biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -783,7 +781,7 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - # TODO: @Eva - replace with PSA test when added to cons list + # TODO: DISCUSSED - but to remain as 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: @@ -885,7 +883,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"MajorSurg": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({"general_bed": 5}) - # todo: @Eva equipment as required for surgery def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -922,7 +919,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment - # TODO: link to surgical equipment package when that exists + # TODO: DISCUSSED -link to surgical equipment package when that exists self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -956,7 +953,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Treatment" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '3' - # todo @Eva - possibly biopsy and histology def apply(self, person_id, squeeze_factor): df = self.sim.population.props @@ -1013,7 +1009,6 @@ def __init__(self, module, person_id): self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({}) self.ACCEPTED_FACILITY_LEVEL = '2' self.BEDDAYS_FOOTPRINT = self.make_beddays_footprint({'general_bed': 15}) - # todo @Eva - generally not sure equipment is required as therapy is with drug, but can require castration surgery def apply(self, person_id, squeeze_factor): df = self.sim.population.props diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 8fc5bd621b..2a833bf9c8 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3205,8 +3205,7 @@ def apply(self, person_id, squeeze_factor): road_traffic_injuries.rti_injury_diagnosis(person_id, self.EXPECTED_APPT_FOOTPRINT) if 'DiagRadio' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): - # TODO: @Eva - use xray package when available - # TODO: @Eva - robbie did not log the 'xray consumable' here as done in other modules (Tb) + # TODO: @Eva - DISCUSSED use xray package when available self.add_equipment({'X-ray machine', 'X-ray viewer'}) elif 'Tomography' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): @@ -3514,11 +3513,13 @@ def apply(self, person_id, squeeze_factor): if df.loc[person_id, 'rt_ISS_score'] > self.hdu_cut_off_iss_score: - # TODO: @Eva some general ICU equipment listed below? additional would need to be added dependent on severity - # of illness - self.add_equipment({ - 'Patient monitor', 'Blood pressure machine', - 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope'}) + # to do @eva/joe - DISCUSSED, make ICU package and use here + self.add_equipment({'Analyser, Combined Chemistry and Electrolytes', + 'Analyser, Haematology', + 'Patient monitor', 'Drip stand', + 'Infusion pump', 'Blood pressure machine', + 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope', + 'Oxygen cylinder, with regulator'}) mean_icu_days = p['mean_icu_days'] sd_icu_days = p['sd_icu_days'] @@ -4098,7 +4099,7 @@ def apply(self, person_id, squeeze_factor): data=f"Fracture casts available for person {person_id} {open_fracture_counts} open fractures" ) - # Todo: @Eva link to surgical equipment package when that exsists + # Todo: DISCUSSED @Eva link to surgical equipment package when that exsists self.add_equipment( {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) @@ -4822,8 +4823,7 @@ def apply(self, person_id, squeeze_factor): 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' - # TODO: @eva - not all surgeries conducted here are laparotomies however likely to have most appropriate equipment - # so should be fine for now + # TODO: @eva - DISCUSSED @eva add surgical package self.add_equipment({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) # ------------------------ Track permanent disabilities with treatment ------------------------------------- @@ -5141,7 +5141,7 @@ 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: - # TODO: @Eva link to surgical equipment package when that exists + # TODO: DISCUSSED @Eva link to surgical equipment package when that exists self.add_equipment({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) # create a dictionary to store the recovery times for each injury in days diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 3ca1a0ae6a..aafcbe0a66 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -2056,7 +2056,8 @@ def apply(self, person_id, squeeze_factor): ) if test_result is not None: # Add used equipment - self.add_equipment({'X-ray machine', 'X-ray viewer'}) # TODO: @Eva make an x-ray pkg with these items + self.add_equipment({'X-ray machine', 'X-ray viewer'}) + # TODO: DISCUSSED @Eva make an x-ray pkg with these items # if consumables not available, refer to level 2 # return blank footprint as xray did not occur @@ -2130,7 +2131,8 @@ def apply(self, person_id, squeeze_factor): ) if test_result is not None: # Add used equipment - self.add_equipment({'X-ray machine', 'X-ray viewer'}) # TODO: Eva make an x-ray pkg with these items + self.add_equipment({'X-ray machine', 'X-ray viewer'}) + # TODO: DISCUSSED Eva make an x-ray pkg with these items # if consumables not available, rely on clinical diagnosis # return blank footprint as xray was not available From 135fc2ec9c8109992e8ae99e2e1cffd928fd9cce Mon Sep 17 00:00:00 2001 From: Eva Janouskova <48157464+EvaJanouskova@users.noreply.github.com> Date: Fri, 31 May 2024 17:02:37 +0200 Subject: [PATCH 435/443] equipment pkgs (#1378) * equip: update lookup pkg names to consider option of having one item within multiple pkgs * RF_EquipCatalogue: pkgs defined * modules: usages of equip pkgs included * RF_EquipCatalogue: Endoscope and ECG added * equip: warning messages clarified * equip & test_equip: fix lookup_item_codes_from_pkg_name() fnc and test its functionality diff --git src/tlo/methods/equipment.py src/tlo/methods/equipment.py index 1759a994f..099226748 100644 --- src/tlo/methods/equipment.py +++ src/tlo/methods/equipment.py @@ -253,8 +253,17 @@ class Equipment: It is expected that this is used by the disease module once and then the resulting equipment item_codes are saved on the module.""" df = self.catalogue + item_codes = set() - if pkg_name not in df['Pkg_Name'].unique().split(", "): + item_codes.update(df.loc[df['Pkg_Name'] == pkg_name, 'Item_Code'].values) + + all_pkg_names = set(df['Pkg_Name'].unique()[~pd.isnull(df['Pkg_Name'].unique())]) + all_multiple_pkg_names = [name for name in all_pkg_names if ", " in name] + for multiple_pkg_name in all_multiple_pkg_names: + if pkg_name in multiple_pkg_name.split(", "): + item_codes.update(df.loc[df['Pkg_Name'] == multiple_pkg_name, 'Item_Code'].values) + + if item_codes: + return item_codes + else: raise ValueError(f'That Pkg_Name is not in the catalogue: {pkg_name=}') - - return set(df.loc[df['Pkg_Name'] == pkg_name, 'Item_Code'].values) diff --git tests/test_equipment.py tests/test_equipment.py index a02ea282f..a39124454 100644 --- tests/test_equipment.py +++ 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": float('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; + # availability not defined for item 3 [ {"Item_Code": 0, "Facility_ID": 0, "Pr_Available": 0.0}, {"Item_Code": 0, "Facility_ID": 1, "Pr_Available": 0.0}, @@ -134,17 +138,22 @@ def test_core_functionality_of_equipment_class(seed): # 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 + # if items are in the same package (once standing alone, once within multiple pkgs defined for item) + assert {0, 1} == eq_default.lookup_item_codes_from_pkg_name(pkg_name='PkgWith0+1') + # if the pkg within multiple pkgs defined for item + assert {1} == eq_default.lookup_item_codes_from_pkg_name(pkg_name='PkgWith1') + # if the pkg only stands alone + assert {3} == eq_default.lookup_item_codes_from_pkg_name(pkg_name='PkgWith3') + # - 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: * equip & test_equip: update lookup for equipment from pkgs to less "expensive" version, lookup fnc renamed to from_pkg_names() diff --git src/tlo/methods/equipment.py src/tlo/methods/equipment.py index 099226748..3a4fb24ba 100644 --- src/tlo/methods/equipment.py +++ 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 @@ class Equipment: # - 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() @@ -248,22 +249,37 @@ class Equipment: 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.""" - df = self.catalogue - item_codes = set() - - item_codes.update(df.loc[df['Pkg_Name'] == pkg_name, 'Item_Code'].values) - - all_pkg_names = set(df['Pkg_Name'].unique()[~pd.isnull(df['Pkg_Name'].unique())]) - all_multiple_pkg_names = [name for name in all_pkg_names if ", " in name] - for multiple_pkg_name in all_multiple_pkg_names: - if pkg_name in multiple_pkg_name.split(", "): - item_codes.update(df.loc[df['Pkg_Name'] == multiple_pkg_name, 'Item_Code'].values) - - if item_codes: - return item_codes + 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: - raise ValueError(f'That Pkg_Name is not in the catalogue: {pkg_name=}') + 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]]: + df = self.catalogue + pkg_lookup = dict() + + pkg_names_raw = set(df['Pkg_Name'].unique()[~pd.isnull(df['Pkg_Name'].unique())]) + all_multiple_pkg_names = set(name for name in pkg_names_raw if ", " in name) + all_pkg_names = pkg_names_raw - all_multiple_pkg_names + for pkg_name in all_pkg_names: + pkg_lookup[pkg_name] = set(df.loc[df['Pkg_Name'] == pkg_name, 'Item_Code'].values) + for multiple_pkg_name in all_multiple_pkg_names: + for pkg_name in multiple_pkg_name.split(", "): + if pkg_name not in all_pkg_names: + pkg_lookup[pkg_name] = set() + all_pkg_names.update({pkg_name}) + pkg_lookup[pkg_name].update(set(df.loc[df['Pkg_Name'] == multiple_pkg_name, 'Item_Code'].values)) + return pkg_lookup diff --git tests/test_equipment.py tests/test_equipment.py index a39124454..0a462f08f 100644 --- tests/test_equipment.py +++ tests/test_equipment.py @@ -139,15 +139,15 @@ def test_core_functionality_of_equipment_class(seed): # 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.lookup_item_codes_from_pkg_name(pkg_name='PkgWith0+1') + assert {0, 1} == eq_default.from_pkg_names(pkg_name='PkgWith0+1') # if the pkg within multiple pkgs defined for item - assert {1} == eq_default.lookup_item_codes_from_pkg_name(pkg_name='PkgWith1') + assert {1} == eq_default.from_pkg_names(pkg_name='PkgWith1') # if the pkg only stands alone - assert {3} == eq_default.lookup_item_codes_from_pkg_name(pkg_name='PkgWith3') + assert {3} == eq_default.from_pkg_names(pkg_name='PkgWith3') # - Error thrown when package is not recognised with pytest.raises(ValueError): - eq_default.lookup_item_codes_from_pkg_name(pkg_name='') + eq_default.from_pkg_names(pkg_names='') equipment_item_code_that_is_available = [0, 1, ] * equip lookup fnc renamed to from_pkg_names() * test_equip: fixes from rebase * [no ci] test_equip: test correct item codes returned for multiple specified pkgs * add ANC packages * edit equipment_availability_estimation.py to manually add cost category * upate ResourceFile_Equipment_Availability_Estimates.csv running new script --------- Co-authored-by: joehcollins Co-authored-by: Tim Hallett <39991060+tbhallett@users.noreply.github.com> --- .../ResourceFile_EquipmentCatalogue.csv | 4 +- ...eFile_Equipment_Availability_Estimates.csv | 4 +- .../equipment_availability_estimation.py | 6 +++ src/tlo/methods/bladder_cancer.py | 4 +- src/tlo/methods/breast_cancer.py | 4 +- src/tlo/methods/cardio_metabolic_disorders.py | 8 +--- .../methods/care_of_women_during_pregnancy.py | 25 +++++----- src/tlo/methods/contraception.py | 2 +- src/tlo/methods/equipment.py | 47 ++++++++++++++----- src/tlo/methods/labour.py | 8 +--- src/tlo/methods/oesophagealcancer.py | 4 +- src/tlo/methods/other_adult_cancers.py | 4 +- src/tlo/methods/prostate_cancer.py | 4 +- src/tlo/methods/rti.py | 25 ++++------ src/tlo/methods/tb.py | 8 +--- tests/test_equipment.py | 26 +++++++--- 16 files changed, 97 insertions(+), 86 deletions(-) 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/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 6c7685eb04..372940b57f 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -897,9 +897,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available and the treatment will go ahead - update the equipment - # TODO: DISCUSSED - replace with package and call here - self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', - 'Blood pressure machine', 'Pulse oximeter'}) + 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 diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index 7279f62d7c..d7f0c7570f 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -796,9 +796,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - add the used equipment - # TODO: DISCUSSED - link to new surgical pacakge - self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', - 'Blood pressure machine', 'Pulse oximeter'}) + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) # Log the use of adjuvant chemotherapy self.get_consumables( diff --git a/src/tlo/methods/cardio_metabolic_disorders.py b/src/tlo/methods/cardio_metabolic_disorders.py index e55fb7ae86..079f4a2cf1 100644 --- a/src/tlo/methods/cardio_metabolic_disorders.py +++ b/src/tlo/methods/cardio_metabolic_disorders.py @@ -1799,13 +1799,7 @@ def apply(self, person_id, squeeze_factor): data=('This is HSI_CardioMetabolicDisorders_SeeksEmergencyCareAndGetsTreatment: ' f'The squeeze-factor is {squeeze_factor}.'), ) - # to do @eva/joe - DISCUSSED, make ICU package and use here - self.add_equipment({'Analyser, Combined Chemistry and Electrolytes', - 'Analyser, Haematology', - 'Patient monitor', 'Drip stand', - 'Infusion pump', 'Blood pressure machine', - 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope', - 'Oxygen cylinder, with regulator'}) + 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 cfbf07e4d0..0fde98d7ed 100644 --- a/src/tlo/methods/care_of_women_during_pregnancy.py +++ b/src/tlo/methods/care_of_women_during_pregnancy.py @@ -1440,8 +1440,9 @@ def apply(self, person_id, squeeze_factor): # =================================== 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( - {'Weighing scale', 'Height Pole (Stadiometer)', 'MUAC tape', 'Measuring tapes', + {'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 @@ -1529,9 +1530,9 @@ def apply(self, person_id, squeeze_factor): # =================================== INTERVENTIONS ==================================================== # 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( - {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' - 'Ultrasound, combined 2/4 pole interferential with vacuum and dual frequency 1-3MHZ'}) + {'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 @@ -1617,8 +1618,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - self.add_equipment({'Weighing scale', 'Measuring tapes', - 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1692,8 +1692,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - self.add_equipment({'Weighing scale', 'Measuring tapes', - 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1762,10 +1761,10 @@ 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( - {'Weighing scale', 'Measuring tapes', 'Stethoscope, foetal, monaural, Pinard, plastic' - 'Ultrasound, combined 2/4 pole interferential with vacuum and dual frequency 1-3MHZ'}) + {'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) @@ -1896,8 +1895,7 @@ def apply(self, person_id, squeeze_factor): df.at[person_id, 'ac_total_anc_visits_current_pregnancy'] += 1 # =================================== INTERVENTIONS ==================================================== - self.add_equipment({'Weighing scale', 'Measuring tapes', - 'Stethoscope, foetal, monaural, Pinard, plastic'}) + 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) @@ -1952,8 +1950,7 @@ 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({'Weighing scale', 'Measuring tapes', - 'Stethoscope, foetal, monaural, Pinard, plastic'}) + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('ANC')) self.module.interventions_delivered_each_visit_from_anc2(hsi_event=self) diff --git a/src/tlo/methods/contraception.py b/src/tlo/methods/contraception.py index 06554564da..0b61aa882e 100644 --- a/src/tlo/methods/contraception.py +++ b/src/tlo/methods/contraception.py @@ -1206,7 +1206,7 @@ def apply(self, person_id, squeeze_factor): self.add_equipment({ 'Cusco’s/ bivalved Speculum (small, medium, large)', 'Lamp, Anglepoise' }) - # TO DO: @Eva - DISCUSSED add equip for minor surgery + 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' diff --git a/src/tlo/methods/equipment.py b/src/tlo/methods/equipment.py index 916927d792..3a4fb24ba9 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,37 @@ 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.""" - df = self.catalogue + 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) - if pkg_name not in df['Pkg_Name'].unique(): - raise ValueError(f'That Pkg_Name is not in the catalogue: {pkg_name=}') + 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 - return set(df.loc[df['Pkg_Name'] == pkg_name, 'Item_Code'].values) + def _create_pkg_lookup(self) -> Dict[str, Set[int]]: + df = self.catalogue + pkg_lookup = dict() + + pkg_names_raw = set(df['Pkg_Name'].unique()[~pd.isnull(df['Pkg_Name'].unique())]) + all_multiple_pkg_names = set(name for name in pkg_names_raw if ", " in name) + all_pkg_names = pkg_names_raw - all_multiple_pkg_names + for pkg_name in all_pkg_names: + pkg_lookup[pkg_name] = set(df.loc[df['Pkg_Name'] == pkg_name, 'Item_Code'].values) + for multiple_pkg_name in all_multiple_pkg_names: + for pkg_name in multiple_pkg_name.split(", "): + if pkg_name not in all_pkg_names: + pkg_lookup[pkg_name] = set() + all_pkg_names.update({pkg_name}) + pkg_lookup[pkg_name].update(set(df.loc[df['Pkg_Name'] == multiple_pkg_name, 'Item_Code'].values)) + return pkg_lookup diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index 9458a4eb03..061619b8cd 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -2128,9 +2128,7 @@ def surgical_management_of_pph(self, hsi_event): if avail and sf_check: # Add used equipment - # Todo: DISCUSSED - @eva surgical package - hsi_event.add_equipment( - {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) + hsi_event.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) # determine if uterine preserving surgery will be successful treatment_success_pph = params['success_rate_pph_surgery'] > self.rng.random_sample() @@ -3236,9 +3234,7 @@ def apply(self, person_id, squeeze_factor): elif (avail and sf_check) or (mni[person_id]['cs_indication'] == 'other'): # If intervention is delivered - add used equipment - # todo: DISCUSSED @Eva link to surgical equipment package when that exsists - self.add_equipment( - {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) + 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()) diff --git a/src/tlo/methods/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index b22d1485ab..e8e0a84452 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -784,9 +784,7 @@ def apply(self, person_id, squeeze_factor): if cons_avail: # If consumables are available and the treatment will go ahead - update the equipment - # TODO: DISCUSSED - add surgery package - self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', - 'Blood pressure machine', 'Pulse oximeter'}) + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) # Log chemotherapy consumables self.get_consumables( diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 74c7abb910..6832cc068c 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -786,9 +786,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment - # TODO: DISCUSSED - link to surgical equipment package when that exists - self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', - 'Blood pressure machine', 'Pulse oximeter'}) + 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 diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index a07d4cea92..1eaed6ee9e 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -918,9 +918,7 @@ def apply(self, person_id, squeeze_factor): if cons_available: # If consumables are available and the treatment will go ahead - update the equipment - # TODO: DISCUSSED -link to surgical equipment package when that exists - self.add_equipment({'Infusion pump', 'Drip stand', 'Laparotomy Set', - 'Blood pressure machine', 'Pulse oximeter'}) + 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 diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 2a833bf9c8..35237589ab 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -3205,8 +3205,7 @@ def apply(self, person_id, squeeze_factor): road_traffic_injuries.rti_injury_diagnosis(person_id, self.EXPECTED_APPT_FOOTPRINT) if 'DiagRadio' in list(self.EXPECTED_APPT_FOOTPRINT.keys()): - # TODO: @Eva - DISCUSSED use xray package when available - self.add_equipment({'X-ray machine', 'X-ray viewer'}) + 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' @@ -3513,13 +3512,7 @@ def apply(self, person_id, squeeze_factor): if df.loc[person_id, 'rt_ISS_score'] > self.hdu_cut_off_iss_score: - # to do @eva/joe - DISCUSSED, make ICU package and use here - self.add_equipment({'Analyser, Combined Chemistry and Electrolytes', - 'Analyser, Haematology', - 'Patient monitor', 'Drip stand', - 'Infusion pump', 'Blood pressure machine', - 'Pulse oximeter', 'Trolley, emergency', 'Stethoscope', - 'Oxygen cylinder, with regulator'}) + 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'] @@ -4099,9 +4092,7 @@ def apply(self, person_id, squeeze_factor): data=f"Fracture casts available for person {person_id} {open_fracture_counts} open fractures" ) - # Todo: DISCUSSED @Eva link to surgical equipment package when that exsists - self.add_equipment( - {'Infusion pump', 'Drip stand', 'Laparotomy Set', 'Blood pressure machine', 'Pulse oximeter'}) + 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 @@ -4823,8 +4814,9 @@ def apply(self, person_id, squeeze_factor): 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' - # TODO: @eva - DISCUSSED @eva add surgical package - self.add_equipment({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) + # TODO: @Joe to confirm major surgery pkg should be used here (as the original set of equipment here was not + # the full pkg) + self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) # ------------------------ Track permanent disabilities with treatment ------------------------------------- # --------------------------------- Perm disability from TBI ----------------------------------------------- @@ -5141,8 +5133,9 @@ 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: - # TODO: DISCUSSED @Eva link to surgical equipment package when that exists - self.add_equipment({'Laparotomy Set', 'Infusion pump', 'Drip stand'}) + # TODO: @Joe to confirm major surgery pkg should be used here (as the original set of equipment here was not + # the full pkg) + 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 = { diff --git a/src/tlo/methods/tb.py b/src/tlo/methods/tb.py index 52a692e1b1..ee83add34e 100644 --- a/src/tlo/methods/tb.py +++ b/src/tlo/methods/tb.py @@ -1958,9 +1958,7 @@ def apply(self, person_id, squeeze_factor): dx_tests_to_run="tb_xray_smear_negative", hsi_event=self ) if test_result is not None: - # Add used equipment - self.add_equipment({'X-ray machine', 'X-ray viewer'}) - # TODO: DISCUSSED @Eva make an x-ray pkg with these items + 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 @@ -2035,9 +2033,7 @@ def apply(self, person_id, squeeze_factor): dx_tests_to_run="tb_xray_smear_negative", hsi_event=self ) if test_result is not None: - # Add used equipment - self.add_equipment({'X-ray machine', 'X-ray viewer'}) - # TODO: DISCUSSED Eva make an x-ray pkg with these items + 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 diff --git a/tests/test_equipment.py b/tests/test_equipment.py index a02ea282f8..d9fbbcdc2c 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( @@ -134,17 +140,25 @@ def test_core_functionality_of_equipment_class(seed): # 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 + # 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'}) + # - Error thrown when package is not recognised with pytest.raises(ValueError): - eq_default.lookup_item_codes_from_pkg_name(pkg_name='') - + eq_default.from_pkg_names(pkg_names='') 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: From b7c27c895aecc707a55d35e9486b084de761cd81 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 6 Jun 2024 11:48:55 +0100 Subject: [PATCH 436/443] cancer_cons: joined screening cons for biopsy, cystoscopy and endoscopy; bc: biopsy replaced with cystoscopy; oc biopsy replaced with endoscopy --- src/tlo/methods/bladder_cancer.py | 14 +++++--------- src/tlo/methods/breast_cancer.py | 3 ++- src/tlo/methods/cancer_consumables.py | 24 +++++------------------- src/tlo/methods/oesophagealcancer.py | 6 +++--- src/tlo/methods/other_adult_cancers.py | 3 ++- src/tlo/methods/prostate_cancer.py | 2 +- 6 files changed, 18 insertions(+), 34 deletions(-) diff --git a/src/tlo/methods/bladder_cancer.py b/src/tlo/methods/bladder_cancer.py index 372940b57f..d5e3bb7d01 100644 --- a/src/tlo/methods/bladder_cancer.py +++ b/src/tlo/methods/bladder_cancer.py @@ -723,11 +723,9 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check consumables are available - # TODO: DISCUSSED - @Eva 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 @@ -798,11 +796,9 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check consumables are available - # TODO: DISCUSSED - @Eva 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 diff --git a/src/tlo/methods/breast_cancer.py b/src/tlo/methods/breast_cancer.py index d7f0c7570f..7c0d2f4c9a 100644 --- a/src/tlo/methods/breast_cancer.py +++ b/src/tlo/methods/breast_cancer.py @@ -694,7 +694,8 @@ 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 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/oesophagealcancer.py b/src/tlo/methods/oesophagealcancer.py index e8e0a84452..320639a9e7 100644 --- a/src/tlo/methods/oesophagealcancer.py +++ b/src/tlo/methods/oesophagealcancer.py @@ -686,10 +686,10 @@ def apply(self, person_id, squeeze_factor): return hs.get_blank_appt_footprint() # Check the consumables are available - # todo: DISCUSS - add endoscope consumable to screening biopsy - 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 add used equipment and run the dx_test representing the biopsy diff --git a/src/tlo/methods/other_adult_cancers.py b/src/tlo/methods/other_adult_cancers.py index 6832cc068c..bfcf14d57c 100644 --- a/src/tlo/methods/other_adult_cancers.py +++ b/src/tlo/methods/other_adult_cancers.py @@ -692,7 +692,8 @@ 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 add used equipment and run the dx_test representing the biopsy diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index 1eaed6ee9e..d79d9db1a5 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -824,7 +824,7 @@ 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 From 1f1d94bac8ac8c622e198167cd9d2180364a7584 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 6 Jun 2024 15:56:47 +0100 Subject: [PATCH 437/443] la: fix calling equipment from outside HSI_event --- src/tlo/methods/labour.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/labour.py b/src/tlo/methods/labour.py index a8babe10a6..64fd303a58 100644 --- a/src/tlo/methods/labour.py +++ b/src/tlo/methods/labour.py @@ -2146,7 +2146,7 @@ def surgical_management_of_pph(self, hsi_event): if avail and sf_check: # Add used equipment - hsi_event.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) + hsi_event.add_equipment(hsi_event.healthcare_system.equipment.from_pkg_names('Major Surgery')) # determine if uterine preserving surgery will be successful treatment_success_pph = params['success_rate_pph_surgery'] > self.rng.random_sample() From 6c8c33da71ba40b6dad43e39dea0997e214b0eb9 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 6 Jun 2024 16:18:49 +0100 Subject: [PATCH 438/443] pc: rm outdated todos --- src/tlo/methods/prostate_cancer.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tlo/methods/prostate_cancer.py b/src/tlo/methods/prostate_cancer.py index d79d9db1a5..9eed6f1375 100644 --- a/src/tlo/methods/prostate_cancer.py +++ b/src/tlo/methods/prostate_cancer.py @@ -733,7 +733,6 @@ def apply(self, person_id, squeeze_factor): ) # Check consumable availability - # TODO: DISCUSSED - but to remain as 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: @@ -781,7 +780,6 @@ def apply(self, person_id, squeeze_factor): hsi_event=self ) - # TODO: DISCUSSED - but to remain as 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: @@ -804,7 +802,6 @@ def __init__(self, module, person_id): self.TREATMENT_ID = "ProstateCancer_Investigation" self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1}) self.ACCEPTED_FACILITY_LEVEL = '1b' - # todo @Eva - biopsy equipment needed (perhaps ultrasound to guide). histology lab equipment. def apply(self, person_id, squeeze_factor): df = self.sim.population.props From 694da75cd460d652dbd8febc4ba64a0bad90a007 Mon Sep 17 00:00:00 2001 From: Eva Janouskova Date: Thu, 6 Jun 2024 16:20:32 +0100 Subject: [PATCH 439/443] rti: both todo @joe moved to a comment in PR --- src/tlo/methods/rti.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/tlo/methods/rti.py b/src/tlo/methods/rti.py index 387cb7ed5f..991357ca8c 100644 --- a/src/tlo/methods/rti.py +++ b/src/tlo/methods/rti.py @@ -4815,8 +4815,6 @@ def apply(self, person_id, squeeze_factor): 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' - # TODO: @Joe to confirm major surgery pkg should be used here (as the original set of equipment here was not - # the full pkg) self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery')) # ------------------------ Track permanent disabilities with treatment ------------------------------------- @@ -5134,8 +5132,6 @@ 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: - # TODO: @Joe to confirm major surgery pkg should be used here (as the original set of equipment here was not - # the full pkg) 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 From 7a27a1f8683eb1bb95b30a6f08e7cbb68891cca6 Mon Sep 17 00:00:00 2001 From: joehcollins Date: Mon, 10 Jun 2024 13:10:50 +0100 Subject: [PATCH 440/443] add surgical pkg to postnatal_supervisor.py --- src/tlo/methods/postnatal_supervisor.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tlo/methods/postnatal_supervisor.py b/src/tlo/methods/postnatal_supervisor.py index 3d9c4ce1fc..25bce6013f 100644 --- a/src/tlo/methods/postnatal_supervisor.py +++ b/src/tlo/methods/postnatal_supervisor.py @@ -1294,7 +1294,7 @@ def apply(self, person_id, squeeze_factor): self.get_consumables(item_codes=of_repair_cons) - # TODO DISCUSSED @Joe - add surgical package + 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( From 3bd3487c4be167440206bd34fd396741e776ee1c Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:47:13 +0100 Subject: [PATCH 441/443] simplify parsing of package name --- src/tlo/methods/equipment.py | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/tlo/methods/equipment.py b/src/tlo/methods/equipment.py index 0a8cfa10b8..56fbe0ac68 100644 --- a/src/tlo/methods/equipment.py +++ b/src/tlo/methods/equipment.py @@ -268,18 +268,23 @@ def from_pkg_names(self, pkg_names: Union[str, Iterable[str]]) -> Set[int]: 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 - pkg_lookup = dict() - - pkg_names_raw = set(df['Pkg_Name'].unique()[~pd.isnull(df['Pkg_Name'].unique())]) - all_multiple_pkg_names = set(name for name in pkg_names_raw if ", " in name) - all_pkg_names = pkg_names_raw - all_multiple_pkg_names - for pkg_name in all_pkg_names: - pkg_lookup[pkg_name] = set(df.loc[df['Pkg_Name'] == pkg_name, 'Item_Code'].values) - for multiple_pkg_name in all_multiple_pkg_names: - for pkg_name in multiple_pkg_name.split(", "): - if pkg_name not in all_pkg_names: - pkg_lookup[pkg_name] = set() - all_pkg_names.update({pkg_name}) - pkg_lookup[pkg_name].update(set(df.loc[df['Pkg_Name'] == multiple_pkg_name, 'Item_Code'].values)) - return pkg_lookup + + # 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.replace(' ', '') \ + .str.get_dummies(sep=',') \ + .set_index(df.Item_Code) \ + .astype(bool) + + # Make dict of the form: {'Pkg_Code': } + pkg_lookup_dict = { + pkg_name: set(pkgs[pkg_name].loc[pkgs[pkg_name]].index.to_list()) + for pkg_name in pkgs.columns + } + + return pkg_lookup_dict From f4aabca3faeaa6024d1ddd766bedc3fc01c1b1f9 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Thu, 20 Jun 2024 13:47:42 +0100 Subject: [PATCH 442/443] put tests for using package name next to other tests to do with parsing --- tests/test_equipment.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/test_equipment.py b/tests/test_equipment.py index d9fbbcdc2c..1167023aa8 100644 --- a/tests/test_equipment.py +++ b/tests/test_equipment.py @@ -82,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) @@ -138,22 +154,6 @@ 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 - # 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'}) - - # - Error thrown when package is not recognised - with pytest.raises(ValueError): - eq_default.from_pkg_names(pkg_names='') - equipment_item_code_that_is_available = [0, 1, ] equipment_item_code_that_is_not_available = [2, 3,] From 88b6f599d297ec21d4cba30a5375038dcbe683f1 Mon Sep 17 00:00:00 2001 From: Tim Hallett <39991060+tbhallett@users.noreply.github.com> Date: Thu, 20 Jun 2024 14:14:01 +0100 Subject: [PATCH 443/443] allow spaces in Pkg_Names (don't remove all spaces, but instead strip out trailing/leading spaces) --- src/tlo/methods/equipment.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tlo/methods/equipment.py b/src/tlo/methods/equipment.py index 56fbe0ac68..e00bf030fd 100644 --- a/src/tlo/methods/equipment.py +++ b/src/tlo/methods/equipment.py @@ -276,14 +276,13 @@ def _create_pkg_lookup(self) -> Dict[str, Set[int]]: # 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.replace(' ', '') \ .str.get_dummies(sep=',') \ .set_index(df.Item_Code) \ .astype(bool) # Make dict of the form: {'Pkg_Code': } pkg_lookup_dict = { - pkg_name: set(pkgs[pkg_name].loc[pkgs[pkg_name]].index.to_list()) + pkg_name.strip(): set(pkgs[pkg_name].loc[pkgs[pkg_name]].index.to_list()) for pkg_name in pkgs.columns }