Skip to content

Commit

Permalink
Editing units of consumable required for each HSI (#1298)
Browse files Browse the repository at this point in the history
* add latest version of costing resource file

* add latest version of costing resource file

- with updated units for cost of consumables

* update Gentamycin (units)

* update item quantities for HIV prep and infant prep

* update 1g to 1mg for Albendazole (Item code 52)

* update units for gloves from 1 glove to "1 pair of gloves"

* update item quantities for TB drugs
add 3HP as new consumable for IPT

* add conditions for each IPT recommendation

* add prices for ARVs
- and create placeholder for Isoniazid/Rifapentine

* add Isoniazid/Rifapentine to `ResourceFile_Consumables_Items_and_Packages.csv`

* add Isoniazid/Rifapentine to all relevant RFs
- 1. `ResourceFile_Consumables_Items_and_Packages.csv` - list of item codes and names
- 2. `ResourceFile_consumabes_matched.csv` - crosswalk between consumables in the TLO model and the OpenLMIS dataset to extract availability (used proxy 'Isoniazid, 100mg' OR 'Isoniazid, 300mg')
- 3. `ResourceFile_Consumables_availability_small.csv` - Final availability estimates - updated by running `consumables_availability_estimation.py`
- 4 `ResourceFile_Costing.xlsx` - cost of tablet based on external web report

* Update to the functionality for requesting consumables across the MNH modules to allow for clearer requesting of units of consumables for costing (correct number of units not yet implemented for all consumables)

* fixes to failing tests

* linting

* linting

* change cotrimoxazole units to mg

* merge in updated unit costs
3hp added and linked to consumables dataset

* added dosage for amitriptyline antidepressant treatment

* added dosage for epilepsy treatment

* fix error in postnatal_supervisor.py leading to failing test

* units added for care_of_women_during_pregnancy.py

* units added for care_of_women_during_pregnancy.py

* units added for labour.py

* fix failing test. initial doses for newborn outcomes/postnatal supervisor

* fix failing test. initial doses for newborn outcomes/postnatal supervisor

* delete temporary ~ file created

* update units of safety box to "1 disposed syringe (100 syringes per box)"

* fix failing test. initial doses for newborn outcomes/postnatal supervisor

* fixed error in labour. COPD consumable unites

* remove consumable packages from diarrhoea.py

* diarrhoea.py consumable units

* cmd.py consumable units

* rti.py consumable units

* alri.py consumable units plus fixed error in diarrhoea.py

* co: get item codes of consumables from item names and define number of units per case in the module (using new chosen units)

* Add cystoscope, endoscope and prostate specific antigen test
- add three consumables to `ResourceFile_Consumables_Items_and_Packages.csv` using the `generate_consumables_item_codes_and_packages.py` script
- This is a replication of commit ff4d072 from PR #1341

* co: alternative consumables chosen by probs

* co: 21 tablets per packet for both types of pills (informed by Emi)

* co: update TODOs

* cancer_cons: consumables updated and required units per case defined (as nmbs of chosen units)

* [no ci] cancer_cons: rm outdated TODOs

* [no ci] co: rm resolved TODOs

* co: rm TODOs as opened an issue to resolve them (#1384)

* cancers: missing arguments added

* add availability data cancer consumables
- Biopsy needle and Specimen container
(This is based on assumptions made in `"05 - Resources/Module-healthsystem/consumables raw files/ResourceFile_hhfa_consumables.xlsx`

* Revert "cancers: missing arguments added"

This reverts commit 5e3d5c0.

* cancer_cons: rm get_consumable_item_codes_cancers() argument

* cancer_cons: names corrections -- item & consumables set

* add depn to health system to copd

* remove packing with dict

* refactoring and adding todo

* fix misnamed tb consumable

* roll back file added by accident

* test_co & co: alternatives rolled back

* roll back second output

* simplify _get_cons_group as no longer changing dose by age

* remove todo, now resolved

* linting and removed completed todo

* linting

---------

Co-authored-by: sm2511 <[email protected]>
Co-authored-by: tdm32 <[email protected]>
Co-authored-by: joehcollins <[email protected]>
Co-authored-by: Tim Hallett <[email protected]>
Co-authored-by: Eva Janouskova <[email protected]>
  • Loading branch information
6 people authored Jun 6, 2024
1 parent 1cab017 commit 6af737a
Show file tree
Hide file tree
Showing 23 changed files with 796 additions and 622 deletions.
3 changes: 3 additions & 0 deletions resources/costing/ResourceFile_Costing.xlsx
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
Original file line number Diff line number Diff line change
Expand Up @@ -277,14 +277,10 @@ def custom_agg(x):
return _collapsed_df

# Hold out the dataframe with no naming inconsistencies
list_of_items_with_inconsistent_names_zipped = list(
zip(inconsistent_item_names_mapping.keys(), inconsistent_item_names_mapping.values()))
list_of_items_with_inconsistent_names = [
item for sublist in list_of_items_with_inconsistent_names_zipped for item in sublist]
df_with_consistent_item_names = lmis_df_wide_flat[~lmis_df_wide_flat[('item',)].isin(
list_of_items_with_inconsistent_names)]
df_without_consistent_item_names = lmis_df_wide_flat[lmis_df_wide_flat[('item',)].isin(
list_of_items_with_inconsistent_names)]
list_of_items_with_inconsistent_names_zipped = set(zip(inconsistent_item_names_mapping.keys(), inconsistent_item_names_mapping.values()))
list_of_items_with_inconsistent_names = [item for sublist in list_of_items_with_inconsistent_names_zipped for item in sublist]
df_with_consistent_item_names = lmis_df_wide_flat[~lmis_df_wide_flat[('item',)].isin(list_of_items_with_inconsistent_names)]
df_without_consistent_item_names = lmis_df_wide_flat[lmis_df_wide_flat[('item',)].isin(list_of_items_with_inconsistent_names)]
# Make inconsistently named drugs uniform across the dataframe
df_without_consistent_item_names_corrected = rename_items_to_address_inconsistentencies(
df_without_consistent_item_names, inconsistent_item_names_mapping)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,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"
Expand Down Expand Up @@ -245,7 +246,7 @@

def add_record(df: pd.DataFrame, record: Dict):
"""Add a row to the bottom of the dataframe, where the row is specified by a dict keyed by the target columns."""
assert list(df.columns) == list(record.keys())
assert set(df.columns) == set(record.keys())
return pd.concat([df, pd.DataFrame.from_records([record])], ignore_index=True)


Expand Down Expand Up @@ -328,6 +329,54 @@ def add_record(df: pd.DataFrame, record: Dict):
},
)

cons = add_record(
cons,
{
'Intervention_Cat': "Added by SM (Recommended by TM)",
'Intervention_Pkg': "Isoniazid preventative therapy for HIV+ no TB",
'Intervention_Pkg_Code': 82,
'Items': "Isoniazid/Rifapentine",
'Item_Code': 2678,
'Expected_Units_Per_Case': 1.0,
'Unit_Cost': 1.0
},
)

cons = add_record(
cons,
{
'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},
)

cons = add_record(
cons,{
'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},
)

cons = add_record(
cons,{
'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},
)


# --------------
# --------------
# --------------
Expand Down
139 changes: 36 additions & 103 deletions src/tlo/methods/alri.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
"""
from __future__ import annotations

import types
from collections import defaultdict
from itertools import chain
from pathlib import Path
Expand Down Expand Up @@ -1014,139 +1013,85 @@ def look_up_consumables(self):

get_item_code = self.sim.modules['HealthSystem'].get_item_code_from_item_name

def get_dosage_for_age_in_months(age_in_whole_months: float, doses_by_age_in_months: Dict[int, float]):
"""Returns the dose corresponding to age, using the lookup provided in `doses`. The format of `doses` is:
{<higher_age_boundary_of_age_group_in_months>: <dose>}."""

for upper_age_bound_in_months, _dose in sorted(doses_by_age_in_months.items()):
if age_in_whole_months < upper_age_bound_in_months:
return _dose
return _dose

# # # # # # Dosages by age # # # # # #
# # # # # # Dosages by weight # # # # # #
# Assuming average weight of 0-5 is 12kg (abstraction). Doses sourced for WHO Pocket book of hospital care for
# children: Second edition 2014

# Antibiotic therapy -------------------
# Antibiotics for non-severe pneumonia - oral amoxicillin for 5 days
# Antibiotics for non-severe pneumonia - oral amoxicillin for 5 days (40mg/kg BD - ((12*40)*2)*5 =4800mg)
self.consumables_used_in_hsi['Amoxicillin_tablet_or_suspension_5days'] = {
get_item_code(item='Amoxycillin 250mg_1000_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{2: 0, 12: 0.006, 36: 0.012, np.inf: 0.018}
),
get_item_code(item='Amoxycillin 125mg/5ml suspension, PFR_0.025_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{2: 0, 12: 1, 36: 2, np.inf: 3}
),
}
get_item_code(item='Amoxycillin 250mg_1000_CMST'): 4800,
get_item_code(item='Amoxycillin 125mg/5ml suspension, PFR_0.025_CMST'): 192} # 25mg/ml - 4800/25

# Antibiotics for non-severe pneumonia - oral amoxicillin for 3 days
# Antibiotics for non-severe pneumonia - oral amoxicillin for 3 days (40mg/kg BD - ((12*40)*2)*3 =2880mg)
self.consumables_used_in_hsi['Amoxicillin_tablet_or_suspension_3days'] = {
get_item_code(item='Amoxycillin 250mg_1000_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{2: 0, 12: 0.01, 36: 0.02, np.inf: 0.03}
),
get_item_code(item='Amoxycillin 125mg/5ml suspension, PFR_0.025_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{2: 0, 12: 1, 36: 2, np.inf: 3}
),
}
get_item_code(item='Amoxycillin 250mg_1000_CMST'): 2880,
get_item_code(item='Amoxycillin 125mg/5ml suspension, PFR_0.025_CMST'): 115} # 25mg/ml - 2880/25

# Antibiotics for non-severe pneumonia - oral amoxicillin for 7 days for young infants only
# Antibiotics for non-severe pneumonia - oral amoxicillin for 7 days for young infants only (40mg/kg BD -
# ((12*40)*2)*7 =6720mg)
self.consumables_used_in_hsi['Amoxicillin_tablet_or_suspension_7days'] = {
get_item_code(item='Amoxycillin 250mg_1000_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{1: 0.004, 2: 0.006, np.inf: 0.01}
),
get_item_code(item='Amoxycillin 125mg/5ml suspension, PFR_0.025_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{1: 0.4, 2: 0.5, np.inf: 1}
),
}
get_item_code(item='Amoxycillin 250mg_1000_CMST'): 6720,
get_item_code(item='Amoxycillin 125mg/5ml suspension, PFR_0.025_CMST'): 269} # 25mg/ml - 6720/25

# Antibiotic therapy for severe pneumonia - ampicillin package
# Amp. dose - 50mg/KG QDS 5 days = (50*12)*4)*5 = 12_000mg
# Gent. dose -7.5mg/kg per day 5 days = (7.5*12)*5 = 450mg
self.consumables_used_in_hsi['1st_line_IV_antibiotics'] = {
get_item_code(item='Ampicillin injection 500mg, PFR_each_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{1: 3.73, 2: 5.6, 4: 8, 12: 16, 36: 24, np.inf: 40}
),
get_item_code(item='Gentamicin Sulphate 40mg/ml, 2ml_each_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{1: 0.7, 2: 1.4, 4: 2.81, 12: 4.69, 36: 7.03, np.inf: 9.37}
),
get_item_code(item='Ampicillin injection 500mg, PFR_each_CMST'): 24, # 500mg vial -12_000/500
get_item_code(item='Gentamicin Sulphate 40mg/ml, 2ml_each_CMST'): 6, # 80mg/2ml = 450/8
get_item_code(item='Cannula iv (winged with injection pot) 16_each_CMST'): 1,
get_item_code(item='Syringe, Autodisable SoloShot IX '): 1
}

# # Antibiotic therapy for severe pneumonia - benzylpenicillin package when ampicillin is not available
# Benpen dose - 50_000IU/KG QDS 5 days = (50_000*12)*4)*5 = 12_000_000IU = 8g (approx)
# Gent. dose -7.5mg/kg per day 5 days = (7.5*12)*5 = 450mg
self.consumables_used_in_hsi['Benzylpenicillin_gentamicin_therapy_for_severe_pneumonia'] = {
get_item_code(item='Benzylpenicillin 3g (5MU), PFR_each_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{1: 2, 2: 5, 4: 8, 12: 15, 36: 24, np.inf: 34}
),
get_item_code(item='Gentamicin Sulphate 40mg/ml, 2ml_each_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{1: 0.7, 2: 1.4, 4: 2.81, 12: 4.69, 36: 7.03, np.inf: 9.37}
),
get_item_code(item='Benzylpenicillin 3g (5MU), PFR_each_CMST'): 8,
get_item_code(item='Gentamicin Sulphate 40mg/ml, 2ml_each_CMST'): 6, # 80mg/2ml = 450/8
get_item_code(item='Cannula iv (winged with injection pot) 16_each_CMST'): 1,
get_item_code(item='Syringe, Autodisable SoloShot IX '): 1
}

# Second line of antibiotics for severe pneumonia, if Staph not suspected
# Ceft. dose = 80mg/kg per day 5 days = (80*12)*5 = 4800mg
self.consumables_used_in_hsi['Ceftriaxone_therapy_for_severe_pneumonia'] = {
get_item_code(item='Ceftriaxone 1g, PFR_each_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{4: 1.5, 12: 3, 36: 5, np.inf: 7}
),
get_item_code(item='Ceftriaxone 1g, PFR_each_CMST'): 1, # smallest unit is 1g
get_item_code(item='Cannula iv (winged with injection pot) 16_each_CMST'): 1,
get_item_code(item='Syringe, Autodisable SoloShot IX '): 1
}

# Second line of antibiotics for severe pneumonia, if Staph is suspected
# Flucox. dose = 50mg/kg QDS 7 days = ((50*12)*4)*7 = 16_800mg
# Oral flucox dose. = same
self.consumables_used_in_hsi['2nd_line_Antibiotic_therapy_for_severe_staph_pneumonia'] = {
get_item_code(item='Flucloxacillin 250mg, vial, PFR_each_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{2: 21, 4: 22.4, 12: 37.3, 36: 67.2, 60: 93.3, np.inf: 140}
),
get_item_code(item='Gentamicin Sulphate 40mg/ml, 2ml_each_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{4: 2.81, 12: 4.69, 36: 7.03, 60: 9.37, np.inf: 13.6}
),
get_item_code(item='Flucloxacillin 250mg, vial, PFR_each_CMST'): 16_800,
get_item_code(item='Gentamicin Sulphate 40mg/ml, 2ml_each_CMST'): 6, # 80mg/2ml = 450/8
get_item_code(item='Cannula iv (winged with injection pot) 16_each_CMST'): 1,
get_item_code(item='Syringe, Autodisable SoloShot IX '): 1,
get_item_code(item='Flucloxacillin 250mg_100_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{4: 0.42, 36: 0.84, 60: 1.68, np.inf: 1.68}
),
}
get_item_code(item='Flucloxacillin 250mg_100_CMST'): 16_800}

# First dose of antibiotic before referral -------------------

# single dose of 7.5mg gent and 50mg/g amp. given
# Referral process in iCCM for severe pneumonia, and at health centres for HIV exposed/infected
self.consumables_used_in_hsi['First_dose_oral_amoxicillin_for_referral'] = {
get_item_code(item='Amoxycillin 250mg_1000_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{12: 0.001, 36: 0.002, np.inf: 0.003}
),
get_item_code(item='Amoxycillin 250mg_1000_CMST'): 250,
}

# Referral process at health centres for severe cases
self.consumables_used_in_hsi['First_dose_IM_antibiotics_for_referral'] = {
get_item_code(item='Ampicillin injection 500mg, PFR_each_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{4: 0.4, 12: 0.8, 36: 1.4, np.inf: 2}
),
get_item_code(item='Gentamicin Sulphate 40mg/ml, 2ml_each_CMST'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{4: 0.56, 12: 0.94, 36: 1.41, np.inf: 1.87}
),
get_item_code(item='Ampicillin injection 500mg, PFR_each_CMST'): 2, # 2 x 500mg vial
get_item_code(item='Gentamicin Sulphate 40mg/ml, 2ml_each_CMST'): 2, # assuming single dose at referral
get_item_code(item='Cannula iv (winged with injection pot) 16_each_CMST'): 1,
get_item_code(item='Syringe, Autodisable SoloShot IX '): 1
}

# Oxygen, pulse oximetry and x-ray -------------------

# Oxygen for hypoxaemia
# Oxygen for hypoxaemia - 5/l per min (Approx) for 3 days ((24*60)*5)*3
self.consumables_used_in_hsi['Oxygen_Therapy'] = {
get_item_code(item='Oxygen, 1000 liters, primarily with oxygen cylinders'): 1,
get_item_code(item='Oxygen, 1000 liters, primarily with oxygen cylinders'): 21_600,
}

# Pulse oximetry
Expand All @@ -1162,10 +1107,7 @@ def get_dosage_for_age_in_months(age_in_whole_months: float, doses_by_age_in_mon
# Optional consumables -------------------
# Paracetamol
self.consumables_used_in_hsi['Paracetamol_tablet'] = {
get_item_code(item='Paracetamol, tablet, 100 mg'):
lambda _age: get_dosage_for_age_in_months(int(_age * 12.0),
{36: 12, np.inf: 18}
),
get_item_code(item='Paracetamol, tablet, 100 mg'): 240, # 20mg/kg
}

# Maintenance of fluids via nasograstric tube
Expand All @@ -1178,11 +1120,6 @@ def get_dosage_for_age_in_months(age_in_whole_months: float, doses_by_age_in_mon
get_item_code(item='Salbutamol sulphate 1mg/ml, 5ml_each_CMST'): 2
}

# Bronchodilator - oral
self.consumables_used_in_hsi['Oral_Brochodilator'] = {
get_item_code(item='Salbutamol, syrup, 2 mg/5 ml'): 1,
get_item_code(item='Salbutamol, tablet, 4 mg'): 1
}

def end_episode(self, person_id):
"""End the episode infection for a person (i.e. reset all properties to show no current infection or
Expand Down Expand Up @@ -2420,11 +2357,7 @@ def _get_cons(self, _arg: Union[str, Tuple[str]]) -> bool:
def _get_cons_group(self, item_group_str: str) -> bool:
"""True if _all_ of a group of consumables (identified by a string) is available."""
if item_group_str is not None:
return self.get_consumables(
item_codes={
k: v(self._age_exact_years) if isinstance(v, types.LambdaType) else v
for k, v in self.module.consumables_used_in_hsi[item_group_str].items()
})
return self.get_consumables(self.module.consumables_used_in_hsi[item_group_str])
else:
raise ValueError('String for the group of consumables not provided')

Expand Down
Loading

0 comments on commit 6af737a

Please sign in to comment.