Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The Equipment Class #1098

Merged
merged 125 commits into from
May 15, 2024
Merged
Show file tree
Hide file tree
Changes from 95 commits
Commits
Show all changes
125 commits
Select commit Hold shift + click to select a range
5209b33
TODO: some modules use equipment when talking about consumables
EvaJanouskova Sep 1, 2023
5158aa2
breast_cancer: dummy used_equipment added where Andrew requested
EvaJanouskova Sep 4, 2023
e554dd7
co: dummy used_equipment added for methods where Emi listed some
EvaJanouskova Sep 4, 2023
e4935e3
healthsystem: annual equipment summary log by fac. level
EvaJanouskova Sep 4, 2023
8b455bd
breast_cancer: mastectomy dummy equipment fixed
EvaJanouskova Sep 5, 2023
9f44e85
equipment_catalogue & utils: new script + a change in utils.py - to c…
EvaJanouskova Sep 6, 2023
0ff003b
equipment_catalogue: PEP8
EvaJanouskova Sep 7, 2023
f736939
healthsystem: sort equipment for log
EvaJanouskova Sep 12, 2023
745d4ab
equipment_catalogue: comment updated
EvaJanouskova Sep 12, 2023
e7c52d8
rti: unified use of consumables/equipment terms
EvaJanouskova Sep 12, 2023
b523d58
hs, brc, co: used_equipment renamed to EQUIPMENT; if equip always sam…
EvaJanouskova Sep 12, 2023
5396269
brc: comment updated
EvaJanouskova Sep 18, 2023
2791c18
brc & co: rm the dummy examples of equipment from modules
EvaJanouskova Sep 20, 2023
07e1ae9
RF_Equipment: equipment catalogue - first draft (from Sakshi)
EvaJanouskova Sep 24, 2023
00270c1
RF_Equipment: equipment catalogue - merge duplicates (round 1)
EvaJanouskova Sep 24, 2023
d486c4d
RF_Equipment: equipment catalogue - merge duplicates (round 2)
EvaJanouskova Sep 27, 2023
860d952
hs: debugging Equipment log - rm sorted
EvaJanouskova Sep 27, 2023
c573fc8
hs: fix sorting of _equip_by_level
EvaJanouskova Sep 27, 2023
373018c
RF_Equipment: equipment catalogue - merge duplicates (round 3)
EvaJanouskova Sep 27, 2023
307efab
RF_Equipment: equipment catalogue - merge duplicates (round 4)
EvaJanouskova Sep 27, 2023
4f2ce6c
RF_Equipment: equipment catalogue - merge duplicates (round 5)
EvaJanouskova Sep 29, 2023
10b9dde
RF_Equipment: equipment catalogue - merge duplicates (round 6) + col …
EvaJanouskova Nov 15, 2023
1f2ff79
RF_Equip: equip item codes added
EvaJanouskova Nov 15, 2023
adde374
codes_to_items_list: new script created
EvaJanouskova Nov 15, 2023
e6013d9
codes_to_items_list: script generalised + PEP 8
EvaJanouskova Nov 15, 2023
a214a88
hs: equipment added to HSIEventDetails
EvaJanouskova Nov 16, 2023
32145b6
equip_catalogue: make catalogues from new logging (equip included in …
EvaJanouskova Nov 17, 2023
d8e299f
hs: sort equipment for logging
EvaJanouskova Nov 19, 2023
598368d
test_hs: assert equipment logging within detailed_hsi_event
EvaJanouskova Nov 19, 2023
e2fdafb
[no ci] hs: typo; Equipment logging removed
EvaJanouskova Nov 23, 2023
acf3a63
equip_catalogue: fix the keys mapping to be done for each run
EvaJanouskova Dec 1, 2023
97f0f3f
equip_catalogue: input hsi event details by which to catalog equipment
EvaJanouskova Dec 2, 2023
1435d22
equip_catalogue: input time period by which to catalog equipment
EvaJanouskova Dec 2, 2023
14a11a7
equip_catalogue: list of requested details can be empty
EvaJanouskova Dec 2, 2023
6d85fe1
equip_catalogue: verify inputs as expected & set output file names in…
EvaJanouskova Dec 2, 2023
934e52d
equip_catalogue: (1) detailed - equip set as string in one row, modul…
EvaJanouskova Dec 6, 2023
bdbd8f7
equip_catalogue: suffix (as input) added for output file names
EvaJanouskova Dec 6, 2023
070454c
hs: structure v2; alri+co: examples to test new structure
EvaJanouskova Jan 13, 2024
99be34a
equip_catalogue: TODO added
EvaJanouskova Jan 13, 2024
5076e3b
equip_catalogue: typo
EvaJanouskova Dec 6, 2023
d47b53e
equip_catalogue: TODO added
EvaJanouskova Jan 13, 2024
057fb44
equip_catalogue: bug fixed
EvaJanouskova Jan 13, 2024
2bf3e8a
brc: TODO added
EvaJanouskova Jan 13, 2024
fa3380f
hs: rm prints
EvaJanouskova Jan 13, 2024
4b7904a
hs: rm old code
EvaJanouskova Jan 13, 2024
8c8bcc5
hs: rm/add accidentally added/rmd commas
EvaJanouskova Jan 13, 2024
6b03edd
hs & alri+co: rename and correct return of fncs related to equipment
EvaJanouskova Jan 15, 2024
c37e1de
hs: log equip item codes instead of names
EvaJanouskova Jan 16, 2024
99ad138
equip_catalogue: updated for logged equip item codes
EvaJanouskova Jan 17, 2024
a281e56
brc: change Andrew suggested
EvaJanouskova Jan 19, 2024
1787ffe
hs: updates for better readability; rm unused code
EvaJanouskova Jan 19, 2024
f961640
hs: PEP8
EvaJanouskova Jan 19, 2024
12142b0
hs: get_equip_item_code_from_item_name fnc updated; ESS.EQUIP as codes
EvaJanouskova Jan 19, 2024
36f0523
equip_catalogue: add item codes to catalogue by requested details (1 …
EvaJanouskova Jan 21, 2024
b40f955
hs: allow adding equip by pkg name(s)
EvaJanouskova Jan 21, 2024
9c8b6ad
co & RF_Equip: an example of usage of equipment pkg
EvaJanouskova Jan 21, 2024
0552e48
utils: use pandas fnc (instead of make one)
EvaJanouskova Jan 24, 2024
0106e20
hs: ESS_EQUIP as HSI_Event's attribute; if settings of ESS_EQUIP forg…
EvaJanouskova Jan 24, 2024
455f4e0
hs: fixed saving _hsi_event_names_missing_ess_equip
EvaJanouskova Jan 24, 2024
503036d
hs: fixed updating _hsi_event_names_missing_ess_equip
EvaJanouskova Jan 29, 2024
8ea0b27
hs: TODO smt odd going on with hsi_event_names_missing_ess_equip warning
EvaJanouskova Jan 29, 2024
7ad13cc
hs: sort hsi_event_names_missing_ess_equip warning
EvaJanouskova Jan 29, 2024
39a5d21
hs: equip_item_and_package_code_lookups renamed to equip_item_and_pac…
EvaJanouskova Jan 29, 2024
9dcf46b
hs: ignore_unknown_equip_names
EvaJanouskova Jan 29, 2024
0bda4d0
hs: warning messages shortened
EvaJanouskova Jan 30, 2024
0d387a4
co: example of setting ess. equip based on condition
EvaJanouskova Mar 21, 2024
b87781e
co: comment updated; TODO added
EvaJanouskova Mar 21, 2024
88632f6
co, hs, RF_Equip, RF_HS_params, test_alri, test_co, test_hs: checking…
EvaJanouskova Mar 21, 2024
d71107a
Merge branch 'master' into EvaJ/equipment/structure_ToRunSim
EvaJanouskova Mar 21, 2024
2518c49
Merge branch 'master' into EvaJ/equipment/structure_ToRunSim
tbhallett Mar 25, 2024
a6fffc9
example test suite
tbhallett Mar 25, 2024
8428d8a
typo and add todo
tbhallett Mar 25, 2024
2a3c8f8
further tests
tbhallett Mar 25, 2024
b1aa9ae
[no_ci] RF_Equip: availabilities changed from probs to True/False val…
EvaJanouskova Mar 25, 2024
ffd5178
ac: rm comments
EvaJanouskova Mar 26, 2024
7c8ea54
hs: rm equip_availability before sim default
EvaJanouskova Mar 26, 2024
6ddd11e
labour: rm comments
EvaJanouskova Mar 26, 2024
4f4d858
hs: rm extra line
EvaJanouskova Mar 26, 2024
f8b0ac1
hs: raise error if 1) ess equip not a set of ints, 2) invalid equip_a…
EvaJanouskova Mar 26, 2024
55772ed
Merge branch 'refs/heads/master' into EvaJ/equipment/structure_ToRunSim
tbhallett May 9, 2024
16752ae
roll back incidental changes
tbhallett May 9, 2024
cd2774e
move `codes_to_items_list` to scripts/data-file-processing
tbhallett May 9, 2024
7a5d991
roll back parsing script
tbhallett May 9, 2024
4781555
squash - basic outline of Equipment class
tbhallett May 9, 2024
27779e6
squash - basic outline of Equipment class
tbhallett May 10, 2024
1988ae2
linting
tbhallett May 10, 2024
13d5a3c
update call in bed-days
tbhallett May 10, 2024
3d4b80d
use hashable type in HSIEventDetails
tbhallett May 12, 2024
3bb2581
update logic and add docstring
tbhallett May 12, 2024
3c16898
linting
tbhallett May 13, 2024
5bcdb38
linting
tbhallett May 13, 2024
3bea20f
Merge branch 'master' into EvaJ/equipment/structure_ToRunSim
tbhallett May 13, 2024
3a8d3da
linting
tbhallett May 13, 2024
dc9d9b9
linting
tbhallett May 13, 2024
ea4027b
provide default for 'equip_availability' in test_alri:get_sim()
tbhallett May 13, 2024
55e3352
Prevent recomputing of probabilities every time availability is updated
willGraham01 May 13, 2024
eba5e39
Remove commentted-out code and pass back return value
willGraham01 May 13, 2024
d3f926f
small updates following chnages from Will
tbhallett May 13, 2024
092a417
Update src/tlo/methods/hsi_event.py
tbhallett May 13, 2024
af05d26
Update src/tlo/methods/hsi_event.py
tbhallett May 13, 2024
8be7782
Update src/tlo/methods/equipment.py
tbhallett May 13, 2024
e0d2d00
Update src/tlo/methods/equipment.py
tbhallett May 13, 2024
1a6fdc9
Update src/tlo/methods/equipment.py
tbhallett May 13, 2024
2579b86
Update src/tlo/methods/equipment.py
tbhallett May 13, 2024
f2ea9cf
Update src/tlo/methods/equipment.py
tbhallett May 13, 2024
cf1d8a5
Update src/tlo/methods/equipment.py
tbhallett May 13, 2024
8e0a353
use property syntax and a setter for `availability` rather than `upda…
tbhallett May 13, 2024
6176413
update typehint and docstring on `parse_items` to be more accurate
tbhallett May 13, 2024
fa214a7
fix typo in `test_HealthSystemChangeParameters`
tbhallett May 13, 2024
3be8363
linting
tbhallett May 14, 2024
959c243
remove update_availability
tbhallett May 14, 2024
cc56948
use continue rather than nested if/else statment
tbhallett May 14, 2024
69e28ca
initialise self._EQUIPMENT in the __init__ so that it specific to the…
tbhallett May 14, 2024
d340e09
Update src/tlo/methods/hsi_event.py
tbhallett May 14, 2024
45b3b41
Update src/tlo/methods/hsi_event.py
tbhallett May 14, 2024
da5222e
Update src/tlo/methods/hsi_event.py
tbhallett May 14, 2024
e7b40b0
Update src/tlo/methods/hsi_event.py
tbhallett May 14, 2024
204877e
Update src/tlo/methods/hsi_event.py
tbhallett May 14, 2024
55f9c88
make checks in `test_core_functionality_of_equipment_class` test for …
tbhallett May 14, 2024
5f52b7b
Merge remote-tracking branch 'origin/EvaJ/equipment/structure_ToRunSi…
tbhallett May 14, 2024
75e02a9
check logging from multiple HSI_events in test_logging_of_equipment_f…
tbhallett May 14, 2024
8549403
Merge remote-tracking branch 'refs/remotes/origin/master' into EvaJ/e…
tbhallett May 14, 2024
81b8c58
roll back incidental changes
tbhallett May 14, 2024
e42e660
remove `codes_to_items_list.py` from this PR
tbhallett May 14, 2024
9370931
Merge branch 'master' into EvaJ/equipment/structure_ToRunSim
matt-graham May 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Git LFS file not shown
Git LFS file not shown
Git LFS file not shown
75 changes: 75 additions & 0 deletions src/scripts/data_file_processing/codes_to_items_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
"""
(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)
1 change: 1 addition & 0 deletions src/tlo/analysis/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
}

Expand Down
3 changes: 2 additions & 1 deletion src/tlo/methods/contraception.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
254 changes: 254 additions & 0 deletions src/tlo/methods/equipment.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import warnings
from collections import defaultdict
from typing import Counter, Iterable, Optional, Set, Union

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.

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 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 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`
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 `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 the line-list of all the facilities in the HealthSystem.

"""

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
# - 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
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 the probability with which equipment items that are available.
self._set_equipment_items_available(availability=availability)

def on_simulation_end(self):
"""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 is changed."""
self._set_equipment_items_available(availability=availability)

def _set_equipment_items_available(self, availability: str):
"""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`."""

# 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
# 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_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()))

# 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) # All probabilities -> 1.0
elif availability == "none":
df = df.mul(0.0) # All probabilities -> 0.0
else:
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]:
"""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). For any item_code/descriptor not recognised, a
`UserWarning` is issued."""

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.')

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:
items = set(items)

items_are_ints = all(isinstance(element, int) for element in items)

if items_are_ints:
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)))

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
probabilities of each equipment item being available and multiplying these together to find the probability
that _all_ are available."""
try:
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 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_all_equipment_available(item_codes=item_codes,
facility_id=facility_id)

def record_use_of_equipment(
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_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 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 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

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 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()))
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 of'
'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 the module."""
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)
Loading
Loading