Skip to content

Commit 36cadc8

Browse files
Add equipment to copd, epilepsy and cancer modules (#1706)
* Add comprehensive equipment to disease modules with facility-level dependencies - Cancer modules (breast, prostate, bladder, oesophageal, other adult cancers): * Added facility-level dependent equipment based on column H specifications * Equipment includes imaging (X-ray, ultrasound), laboratory analysis, and cancer-specific tools * Probabilities vary by facility level (1a=community, 1b+=hospitals) - COPD module: * Added moderate exacerbation treatment HSI with respiratory equipment * Incentive spirometers (100% availability) and blood gas analyzers (facility-dependent) * Enhanced severe exacerbation equipment with respiratory support tools - Epilepsy module: * Added comprehensive rehabilitation equipment for severe epilepsy cases * Pediatric-specific equipment for children with epilepsy * Assessment tools, mobility aids, and physiotherapy equipment - Fixed equipment codes: * 454 202 (X-ray machine) * 453 161 (Ultrasound scanning machine) All equipment codes validated against ResourceFile_EquipmentCatalogue.csv * Simplify equipment allocation across cancer treatment modules - Remove probability-based equipment allocation conditions - Convert equipment codes to descriptive names - Consolidate multiple add_equipment() calls into single calls - Focus on 'ever use' approach for facility planning - Applies to: bladder_cancer, breast_cancer, copd, epilepsy, oesophagealcancer, other_adult_cancers, prostate_cancer Addresses PR feedback to eliminate hard-coded probability parameters. * Remove unnecessary random number generation and magic number probabilities - Remove all instances of 'if self.module.rng.random() < 1.0:' (100% probability) - Combine multiple equipment additions into single calls for efficiency - Simplify equipment allocation by removing always-true conditional checks - Addresses PR reviewer feedback about magic numbers and random generation Files updated: - prostate_cancer.py: Fixed 4 instances of unnecessary random checks - oesophagealcancer.py: Fixed 1 instance for safety equipment - other_adult_cancers.py: Fixed 1 instance for safety equipment * Address reviewer feedback: use equipment names, combine add_equipment calls, remove random probabilities - Replace numeric equipment codes with descriptive names (e.g., {50} -> 'Safety Goggles') - Combine multiple add_equipment() calls into single statements for better readability - Remove all random number generation and magic number probabilities from HSI equipment allocation - Maintain 'if equipment is ever used, it should be available' philosophy per reviewer guidance Fixes issues in: - other_adult_cancers.py: Combined equipment calls in palliative care HSI - prostate_cancer.py: Removed random probabilities, combined equipment statements, used names - oesophagealcancer.py: Removed all random equipment allocation, used equipment names --------- Co-authored-by: Tim Hallett <[email protected]>
1 parent 6198ef2 commit 36cadc8

File tree

7 files changed

+416
-17
lines changed

7 files changed

+416
-17
lines changed

src/tlo/methods/bladder_cancer.py

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,16 @@ def apply(self, person_id, squeeze_factor):
733733
if cons_avail:
734734
# Use a biopsy to diagnose whether the person has bladder Cancer
735735
# If consumables are available update the use of equipment and run the dx_test representing the biopsy
736-
self.add_equipment({'Cystoscope', 'Ordinary Microscope', 'Ultrasound scanning machine'})
736+
self.add_equipment({
737+
'Cystoscope',
738+
'Ordinary Microscope',
739+
'Ultrasound scanning machine',
740+
'Analyser, Haematology',
741+
'Analyser, Chemistry',
742+
'X-ray machine',
743+
'Sample Rack',
744+
'Safety Goggles'
745+
})
737746

738747
# Use a cystoscope to diagnose whether the person has bladder Cancer:
739748
dx_result = hs.dx_manager.run_dx_test(
@@ -806,7 +815,16 @@ def apply(self, person_id, squeeze_factor):
806815
if cons_avail:
807816
# Use a biopsy to diagnose whether the person has bladder Cancer
808817
# If consumables are available log the use of equipment and run the dx_test representing the biopsy
809-
self.add_equipment({'Cystoscope', 'Ordinary Microscope', 'Ultrasound scanning machine'})
818+
self.add_equipment({
819+
'Cystoscope',
820+
'Ordinary Microscope',
821+
'Ultrasound scanning machine',
822+
'Analyser, Haematology',
823+
'Analyser, Chemistry',
824+
'X-ray machine',
825+
'Sample Rack',
826+
'Safety Goggles'
827+
})
810828

811829
# Use a cystoscope to diagnose whether the person has bladder Cancer:
812830
dx_result = hs.dx_manager.run_dx_test(
@@ -899,6 +917,19 @@ def apply(self, person_id, squeeze_factor):
899917
# If consumables are available and the treatment will go ahead - update the equipment
900918
self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery'))
901919

920+
# Additional cancer treatment equipment for bladder cancer surgery
921+
self.add_equipment({
922+
'Analyser, Haematology',
923+
'Analyser, Chemistry',
924+
'Analyser, Hormones',
925+
'X-ray machine',
926+
'Ultrasound scanning machine',
927+
'Cystoscope',
928+
'Sample Rack',
929+
'Ordinary Microscope',
930+
'Safety Goggles'
931+
})
932+
902933
# Record date and stage of starting treatment
903934
df.at[person_id, "bc_date_treatment"] = self.sim.date
904935
df.at[person_id, "bc_stage_at_which_treatment_given"] = df.at[person_id, "bc_status"]
@@ -942,6 +973,17 @@ def apply(self, person_id, squeeze_factor):
942973
assert not pd.isnull(df.at[person_id, "bc_date_diagnosis"])
943974
assert not pd.isnull(df.at[person_id, "bc_date_treatment"])
944975

976+
# Equipment for post-treatment monitoring and follow-up
977+
self.add_equipment({
978+
'Analyser, Haematology',
979+
'Analyser, Chemistry',
980+
'X-ray machine',
981+
'Ultrasound scanning machine',
982+
'Cystoscope',
983+
'Sample Rack',
984+
'Safety Goggles'
985+
})
986+
945987
if df.at[person_id, 'bc_status'] == 'metastatic':
946988
# If has progressed to metastatic, then start Palliative Care immediately:
947989
hs.schedule_hsi_event(
@@ -1002,7 +1044,16 @@ def apply(self, person_id, squeeze_factor):
10021044

10031045
if cons_available:
10041046
# If consumables are available and the treatment will go ahead - update the equipment
1005-
self.add_equipment({'Infusion pump', 'Drip stand'})
1047+
self.add_equipment({
1048+
'Infusion pump',
1049+
'Drip stand',
1050+
'Analyser, Haematology',
1051+
'Analyser, Chemistry',
1052+
'X-ray machine',
1053+
'Ultrasound scanning machine',
1054+
'Sample Rack',
1055+
'Safety Goggles'
1056+
})
10061057

10071058
# Record the start of palliative care if this is first appointment
10081059
if pd.isnull(df.at[person_id, "bc_date_palliative_care"]):

src/tlo/methods/breast_cancer.py

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,26 @@ def apply(self, person_id, squeeze_factor):
704704
if cons_avail:
705705
# Use a biopsy to diagnose whether the person has breast Cancer
706706
# If consumables are available, add the used equipment and run the dx_test representing the biopsy
707-
self.add_equipment({'Ultrasound scanning machine', 'Ordinary Microscope'})
707+
708+
# Equipment for breast cancer biopsy and diagnosis
709+
self.add_equipment({
710+
'Ultrasound scanning machine',
711+
'Ordinary Microscope',
712+
'Cusco\'s/ bivalved Speculum',
713+
'Biological Microscopes',
714+
'Examination couch',
715+
'Lamp, Anglepoise',
716+
'Analyser, Haematology',
717+
'Analyzer, Clinical immunoassay',
718+
'Anatomical marker L-R',
719+
'Analyser, Hormones',
720+
'Apron protective x-ray lead',
721+
'Safety Goggles',
722+
'Magnetic resonance imaging (MRI)',
723+
'Manual Rotary Microtome',
724+
'Sample Rack',
725+
'Shaker'
726+
})
708727

709728
dx_result = hs.dx_manager.run_dx_test(
710729
dx_tests_to_run='biopsy_for_breast_cancer_given_breast_lump_discernible',
@@ -802,8 +821,32 @@ def apply(self, person_id, squeeze_factor):
802821

803822
if cons_available:
804823
# If consumables are available and the treatment will go ahead - add the used equipment
824+
# Major surgery equipment package (already includes core surgical equipment)
805825
self.add_equipment(self.healthcare_system.equipment.from_pkg_names('Major Surgery'))
806826

827+
# Additional breast cancer treatment equipment
828+
self.add_equipment({
829+
'Infusion pump',
830+
'Drip stand',
831+
'Pulse oximeter',
832+
'Blood pressure machine',
833+
'Laparotomy Set',
834+
'Examination couch',
835+
'Light, operating, mobile',
836+
'Trolley, emergency',
837+
'Analyser, Haematology',
838+
'Analyzer, Clinical immunoassay',
839+
'Automatic Cell washer',
840+
'Backsplit cotton gown',
841+
'Coagulation machine',
842+
'Sterilizing unit, steam, medium, 240 litre',
843+
'Magnetic Stirrer',
844+
'Micropipettes 10 - 100ul',
845+
'Flow Cytometer',
846+
'Bone Densitometry',
847+
'Automatic staining machine'
848+
})
849+
807850
# Log the use of adjuvant chemotherapy
808851
self.get_consumables(
809852
item_codes=self.module.item_codes_breast_can['treatment_chemotherapy'],
@@ -852,6 +895,25 @@ def apply(self, person_id, squeeze_factor):
852895
assert not pd.isnull(df.at[person_id, "brc_date_diagnosis"])
853896
assert not pd.isnull(df.at[person_id, "brc_date_treatment"])
854897

898+
# Equipment for breast cancer follow-up monitoring
899+
self.add_equipment({
900+
'Ultrasound scanning machine',
901+
'Examination couch',
902+
'Lamp, Anglepoise',
903+
'Ordinary Microscope',
904+
'Blood pressure machine',
905+
'Pulse oximeter',
906+
'Computed Tomography',
907+
'Analyser, Haematology',
908+
'Analyzer, Clinical immunoassay',
909+
'Analyser, Hormones',
910+
'Coagulation machine',
911+
'Bone Densitometry',
912+
'Backsplit cotton gown',
913+
'Safety Goggles',
914+
'Sample Rack'
915+
})
916+
855917
if df.at[person_id, 'brc_status'] == 'stage4':
856918
# If has progressed to stage4, then start Palliative Care immediately:
857919
hs.schedule_hsi_event(
@@ -913,7 +975,26 @@ def apply(self, person_id, squeeze_factor):
913975

914976
if cons_available:
915977
# If consumables are available and the treatment will go ahead - add the used equipment
916-
self.add_equipment({'Infusion pump', 'Drip stand'})
978+
# Equipment for palliative care
979+
self.add_equipment({
980+
'Infusion pump',
981+
'Drip stand',
982+
'Bed, adult',
983+
'Mattress for hospital bed',
984+
'Blood pressure machine',
985+
'Pulse oximeter',
986+
'Trolley, emergency',
987+
'Examination couch',
988+
'Stethoscope',
989+
'Analyser, Haematology',
990+
'Analyzer, Clinical immunoassay',
991+
'Backsplit cotton gown',
992+
'Coagulation machine',
993+
'Automatic Cell washer',
994+
'Sterilizing unit, steam, 39 ltr',
995+
'Safety Goggles',
996+
'Sample Rack'
997+
})
917998

918999
# Record the start of palliative care if this is first appointment
9191000
if pd.isnull(df.at[person_id, "brc_date_palliative_care"]):

src/tlo/methods/copd.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,6 +229,16 @@ def _common_first_appt(
229229
{self.item_codes["bronchodilater_inhaler"]: 1}
230230
):
231231
individual_properties["ch_has_inhaler"] = True
232+
233+
# Schedule moderate COPD treatment for moderate cases (to handle equipment)
234+
if "breathless_moderate" in symptoms:
235+
event = HSI_Copd_TreatmentOnModerateExacerbation(
236+
module=self, person_id=person_id
237+
)
238+
schedule_hsi_event(
239+
event, topen=self.sim.date, priority=0
240+
)
241+
232242
if "breathless_severe" in symptoms:
233243
event = HSI_Copd_TreatmentOnSevereExacerbation(
234244
module=self, person_id=person_id
@@ -541,8 +551,9 @@ def __init__(self, module, person_id):
541551
super().__init__(module, person_id=person_id)
542552

543553
def apply(self, person_id):
544-
df = self.sim.population.props
545-
person = df.loc[person_id, ['is_alive', 'age_years', 'ch_will_die_this_episode', 'ch_lungfunction']]
554+
person = self.sim.population.props.loc[
555+
person_id, ['is_alive', 'age_years', 'ch_will_die_this_episode', 'ch_lungfunction']
556+
]
546557
# Check if an individual should still die and, if so, cause the death
547558
if person.is_alive and person.ch_will_die_this_episode:
548559
self.sim.modules['Demography'].do_death(
@@ -552,6 +563,30 @@ def apply(self, person_id):
552563
)
553564

554565

566+
class HSI_Copd_TreatmentOnModerateExacerbation(HSI_Event, IndividualScopeEventMixin):
567+
"""HSI event for issuing treatment to individuals with moderate COPD exacerbation.
568+
This represents community-based care for non-severe breathlessness."""
569+
570+
def __init__(self, module, person_id):
571+
super().__init__(module, person_id=person_id)
572+
573+
self.TREATMENT_ID = "Copd_Treatment_Moderate"
574+
self.ACCEPTED_FACILITY_LEVEL = "1a"
575+
self.EXPECTED_APPT_FOOTPRINT = self.make_appt_footprint({"Over5OPD": 1})
576+
577+
def apply(self, person_id, squeeze_factor):
578+
"""Treatment for moderate COPD exacerbation - community-based care."""
579+
580+
# Equipment for moderate COPD cases
581+
self.add_equipment({
582+
'Incentive spirometers',
583+
'Analyser, Blood Gas'
584+
})
585+
586+
# Give bronchodilator treatment if available
587+
self.get_consumables({self.module.item_codes['bronchodilater_inhaler']: 1})
588+
589+
555590
class HSI_Copd_TreatmentOnSevereExacerbation(HSI_Event, IndividualScopeEventMixin):
556591
""" HSI event for issuing treatment to all individuals with severe exacerbation. We first check the availability of
557592
oxygen at the default facility(1a). If no oxygen is found we refer individuals to the next higher level facility
@@ -590,7 +625,19 @@ def apply(self, person_id, squeeze_factor):
590625
oxygen=self.get_consumables({self.module.item_codes['oxygen']: 23_040}),
591626
aminophylline=self.get_consumables({self.module.item_codes['aminophylline']: 600})
592627
)
593-
self.add_equipment({'Oxygen cylinder, with regulator', 'Nasal Prongs', 'Drip stand', 'Infusion pump'})
628+
629+
# Equipment for severe COPD exacerbations
630+
self.add_equipment({
631+
'Oxygen cylinder, with regulator',
632+
'Nasal Prongs',
633+
'Drip stand',
634+
'Infusion pump',
635+
'Incentive spirometers',
636+
'Analyser, Blood Gas',
637+
'Ambu bag, adult with mask',
638+
'Postural Drainage Couch',
639+
'Resuscitator'
640+
})
594641

595642
if prob_treatment_success:
596643
df.at[person_id, 'ch_will_die_this_episode'] = False

src/tlo/methods/epilepsy.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,6 +637,21 @@ def apply(self, person_id, squeeze_factor):
637637
df = self.sim.population.props
638638
hs = self.sim.modules["HealthSystem"]
639639

640+
# Add equipment for severe epilepsy cases
641+
if df.at[person_id, 'ep_seiz_stat'] == '3': # Frequent seizures
642+
# Assessment and mobility equipment for severe epilepsy
643+
self.add_equipment({'Wheelchair'})
644+
645+
# Pediatric equipment for children with severe epilepsy
646+
if df.at[person_id, 'age_years'] < 18:
647+
self.add_equipment({
648+
'Paediatric Corner sit',
649+
'Paediatric CP Chair',
650+
'Paediatric mat',
651+
'Paediatric rollator',
652+
'Paediatric Standing frame'
653+
})
654+
640655
# Check what drugs are available
641656
best_available_medicine = self.module.get_best_available_medicine(self)
642657

@@ -703,6 +718,51 @@ def apply(self, person_id, squeeze_factor):
703718
if not df.at[person_id, 'ep_antiep']:
704719
return hs.get_blank_appt_footprint()
705720

721+
# Add equipment for severe epilepsy cases at follow-up visits
722+
if df.at[person_id, 'ep_seiz_stat'] == '3': # Frequent seizures
723+
# Equipment for severe epilepsy rehabilitation and support
724+
self.add_equipment({
725+
'Adaptive communication switches (Infrared switches)',
726+
'Speech therapy kit',
727+
'Bobath bed',
728+
'Box and Block Test',
729+
'Built Up (adapted) Utensils',
730+
'Goniometer',
731+
'Grasp Dynamometer',
732+
'Hand function kits',
733+
'Hot Pack Therapy Units',
734+
'Interferential therapy machine',
735+
'Muscle stimulator',
736+
'TENs Unit',
737+
'Transcutaneous electrical neuromuscular stimulation',
738+
'Exercise Mats',
739+
'Gym mat',
740+
'Parallel Bars',
741+
'Pulley System',
742+
'Suspension Slings',
743+
'Rehabilitation wall bars',
744+
'Rollators',
745+
'Walking Frame',
746+
'Walking Cane',
747+
'Overhead pulley',
748+
'Training stairs',
749+
'Stress Ball',
750+
'Wheelchair',
751+
'Ordinary balls',
752+
'Medicinal Balls (Sets of 0.5kgs, 1kg, 2kgs, 3kgs, 4kgs, 5kgs)',
753+
'Exercise ball'
754+
})
755+
756+
# Pediatric equipment for children with severe epilepsy
757+
if df.at[person_id, 'age_years'] < 18:
758+
self.add_equipment({
759+
'Paediatric Corner sit',
760+
'Paediatric CP Chair',
761+
'Paediatric mat',
762+
'Paediatric rollator',
763+
'Paediatric Standing frame'
764+
})
765+
706766
# Request the medicine
707767
best_available_medicine = self.module.get_best_available_medicine(self)
708768
if best_available_medicine is not None:

0 commit comments

Comments
 (0)