Skip to content

Commit cf25da3

Browse files
Dynamic rescaling of HR resources (#1269)
1 parent 7c31105 commit cf25da3

3 files changed

Lines changed: 115 additions & 2 deletions

File tree

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:187302cf1744ee6a9538665c5dff5650f58e4d00c8d74844ff8b569b6b38f9d1
3-
size 335
2+
oid sha256:e36cbd225191f893f2de5f0f34adc251046af5ec58daaf7c86b09a6c83c1e31d
3+
size 379

src/tlo/methods/healthsystem.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,16 @@ class HealthSystem(Module):
554554
"and custom (user can freely set these factors as parameters in the analysis).",
555555
),
556556

557+
'dynamic_HR_scaling_factor': Parameter(
558+
Types.REAL, "Factor by which HR capabilities are scaled at regular intervals of 1 year (in addition to the"
559+
" [optional] scaling with population size (controlled by `scale_HR_by_popsize`"
560+
),
561+
562+
'scale_HR_by_popsize': Parameter(
563+
Types.BOOL, "Decide whether to scale HR capabilities by population size every year. Can be used as well as"
564+
" the dynamic_HR_scaling_factor"
565+
),
566+
557567
'tclose_overwrite': Parameter(
558568
Types.INT, "Decide whether to overwrite tclose variables assigned by disease modules"),
559569

@@ -904,6 +914,11 @@ def initialise_simulation(self, sim):
904914
sim.schedule_event(HealthSystemChangeMode(self),
905915
Date(self.parameters["year_mode_switch"], 1, 1))
906916

917+
# Schedule recurring event which will rescale daily capabilities at regular intervals.
918+
# The first event scheduled will only be used to update self.last_year_pop_size parameter,
919+
# actual scaling will only take effect from 2011 onwards
920+
sim.schedule_event(DynamicRescalingHRCapabilities(self), Date(sim.date) + pd.DateOffset(years=1))
921+
907922
def on_birth(self, mother_id, child_id):
908923
self.bed_days.on_birth(self.sim.population.props, mother_id, child_id)
909924

@@ -2842,6 +2857,33 @@ def apply(self, population):
28422857
self.module.bed_days.availability = self._parameters['beds_availability']
28432858

28442859

2860+
class DynamicRescalingHRCapabilities(RegularEvent, PopulationScopeEventMixin):
2861+
""" This event exists to scale the daily capabilities assumed at fixed time intervals"""
2862+
def __init__(self, module):
2863+
super().__init__(module, frequency=DateOffset(years=1))
2864+
self.last_year_pop_size = self.current_pop_size # store population size at initiation (when this class is
2865+
# created)
2866+
2867+
@property
2868+
def current_pop_size(self) -> float:
2869+
"""Returns current population size"""
2870+
df = self.sim.population.props
2871+
return df.is_alive.sum()
2872+
2873+
def apply(self, population):
2874+
2875+
this_year_pop_size = self.current_pop_size
2876+
2877+
# Rescale by fixed amount
2878+
self.module._daily_capabilities *= self.module.parameters['dynamic_HR_scaling_factor']
2879+
2880+
# Rescale daily capabilities by population size, if this option is included
2881+
if self.module.parameters['scale_HR_by_popsize']:
2882+
self.module._daily_capabilities *= this_year_pop_size/self.last_year_pop_size
2883+
2884+
self.last_year_pop_size = this_year_pop_size # Save for next year
2885+
2886+
28452887
class HealthSystemChangeMode(RegularEvent, PopulationScopeEventMixin):
28462888
""" This event exists to change the priority policy adopted by the
28472889
HealthSystem at a given year. """

tests/test_healthsystem.py

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2276,4 +2276,75 @@ def get_capabilities_today(const_HR_scaling_mode: str) -> pd.Series:
22762276
)
22772277

22782278

2279+
def test_dynamic_HR_scaling(seed, tmpdir):
2280+
"""Check that we can scale the minutes of time available for healthcare workers on a yearly basis based on either
2281+
a fixed scaling factor or population grown, or both."""
22792282

2283+
def get_initial_capabilities() -> pd.Series:
2284+
sim = Simulation(start_date=start_date, seed=seed)
2285+
sim.register(
2286+
demography.Demography(resourcefilepath=resourcefilepath),
2287+
healthsystem.HealthSystem(resourcefilepath=resourcefilepath)
2288+
)
2289+
sim.make_initial_population(n=100)
2290+
sim.simulate(end_date=start_date + pd.DateOffset(days=0))
2291+
2292+
return sim.modules['HealthSystem'].capabilities_today
2293+
2294+
def get_capabilities_after_two_years(dynamic_HR_scaling_factor: float, scale_HR_by_pop_size: bool) -> tuple:
2295+
sim = Simulation(start_date=start_date, seed=seed)
2296+
sim.register(
2297+
demography.Demography(resourcefilepath=resourcefilepath),
2298+
healthsystem.HealthSystem(resourcefilepath=resourcefilepath),
2299+
simplified_births.SimplifiedBirths(resourcefilepath=resourcefilepath),
2300+
2301+
)
2302+
sim.modules['HealthSystem'].parameters['dynamic_HR_scaling_factor'] = dynamic_HR_scaling_factor
2303+
sim.modules['HealthSystem'].parameters['scale_HR_by_popsize'] = scale_HR_by_pop_size
2304+
sim.make_initial_population(n=100)
2305+
2306+
# Ensure simulation lasts long enough so that current capabilities reflect that used in the third year of
2307+
# simulation (i.e. after two annual updates)
2308+
sim.simulate(end_date=start_date + pd.DateOffset(years=2, days=1))
2309+
2310+
popsize = sim.modules['Demography'].popsize_by_year
2311+
2312+
final_popsize_increase = popsize[2012]/popsize[2010]
2313+
2314+
return sim.modules['HealthSystem'].capabilities_today, final_popsize_increase
2315+
2316+
dynamic_HR_scaling_factor = 1.05
2317+
2318+
# Get initial capabilities and remove all officers with no minutes available
2319+
initial_caps = get_initial_capabilities()
2320+
initial_caps = initial_caps[initial_caps != 0]
2321+
2322+
# Check that dynamic expansion over two years leads to expansion = dynamic_HR_scaling_factor^2
2323+
caps, final_popsize_increase = get_capabilities_after_two_years(
2324+
dynamic_HR_scaling_factor=dynamic_HR_scaling_factor,
2325+
scale_HR_by_pop_size=False
2326+
)
2327+
caps = caps[caps != 0]
2328+
ratio_in_sim = caps/initial_caps
2329+
expected_value = dynamic_HR_scaling_factor*dynamic_HR_scaling_factor
2330+
assert np.allclose(ratio_in_sim, expected_value)
2331+
2332+
# Check that expansion over two years with scaling prop to pop expansion works as expected
2333+
caps, final_popsize_increase = get_capabilities_after_two_years(
2334+
dynamic_HR_scaling_factor=1.0,
2335+
scale_HR_by_pop_size=True
2336+
)
2337+
caps = caps[caps != 0]
2338+
ratio_in_sim = caps/initial_caps
2339+
expected_value = final_popsize_increase
2340+
assert np.allclose(ratio_in_sim, expected_value)
2341+
2342+
# Check that expansion over two years with both fixed scaling and pop expansion scaling works as expected
2343+
caps, final_popsize_increase = get_capabilities_after_two_years(
2344+
dynamic_HR_scaling_factor=dynamic_HR_scaling_factor,
2345+
scale_HR_by_pop_size=True
2346+
)
2347+
caps = caps[caps != 0]
2348+
ratio_in_sim = caps/initial_caps
2349+
expected_value = final_popsize_increase*dynamic_HR_scaling_factor*dynamic_HR_scaling_factor
2350+
assert np.allclose(ratio_in_sim, expected_value)

0 commit comments

Comments
 (0)