From 07b12f59eb88065c16aae7751ee574ba37c639e9 Mon Sep 17 00:00:00 2001 From: PempheroM Date: Thu, 13 Nov 2025 10:58:12 +0200 Subject: [PATCH 1/5] scenario file --- .../nurses_scenario_analyses.py | 158 ++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100644 src/scripts/nurses_analyses/nurses_scenario_analyses.py diff --git a/src/scripts/nurses_analyses/nurses_scenario_analyses.py b/src/scripts/nurses_analyses/nurses_scenario_analyses.py new file mode 100644 index 0000000000..b61e5fa44d --- /dev/null +++ b/src/scripts/nurses_analyses/nurses_scenario_analyses.py @@ -0,0 +1,158 @@ +""" +This scenario file sets up the scenarios for simulating the effects of nursing staffing levels +The scenario +0- Baseline scenario +1- +2- + + +""" +from pathlib import Path +from typing import Dict + +import pandas as pd + +from tlo import Date, logging +from tlo.analysis.utils import get_parameters_for_status_quo, mix_scenarios +from tlo.methods.fullmodel import fullmodel +from tlo.methods.scenario_switcher import ImprovedHealthSystemAndCareSeekingScenarioSwitcher +from tlo.scenario import BaseScenario + + +class StaffingScenario(BaseScenario): + def __init__(self): + super().__init__() + self.seed=12 + self.start_date=Date(2010, 1, 1) + self.end_date=Date(2030, 1, 1) + self.initial_population_size=200 + self.number_of_draws=2 + self.runs_per_draw=2 + + def log_configuration(self): + return { + 'filename': 'nurses_scenario_outputs', + 'directory': Path('./outputs'), # <- (specified only for local running) + 'custom_levels': { + '*': logging.WARNING, + 'tlo.methods.demography': logging.INFO, + 'tlo.methods.demography.detail': logging.WARNING, + 'tlo.methods.healthburden': logging.INFO, + 'tlo.methods.healthsystem.summary': logging.INFO, + } + } + + def modules(self): + return fullmodel(resourcefilepath=self.resources) + [ImprovedHealthSystemAndCareSeekingScenarioSwitcher(resourcefilepath=self.resources)] + + def draw_parameters(self, draw_number, rng): + if draw_number < self.number_of_draws: + return list(self._scenarios.values())[draw_number] + else: + return + + def _get_scenarios(self) -> Dict[str, Dict]: + """Return the Dict with values for the parameters that are changed, keyed by a name for the scenario. + """ + return { + "Baseline": + mix_scenarios( + get_parameters_for_status_quo(), + { + "HealthSystem": { + "ResourceFile_HR_scaling_by_level_and_officer_type": "default", + "year_mode_switch": 2025, + "mode_appt_constraints_postSwitch": 2, + "scale_to_effective_capabilities": True, + "policy_name": "Naive", + "tclose_overwrite": 1, + "tclose_days_offset_overwrite": 7, + "use_funded_or_actual_staffing": "actual", + "year_cons_availability_switch": 2025, + "cons_availability_postSwitch": "all", + }, + } + ), + + "Improved Staffing": + mix_scenarios( + get_parameters_for_status_quo(), + { + "HealthSystem": { + "ResourceFile_HR_scaling_by_level_and_officer_type": "default", + "year_mode_switch": 2025, + "mode_appt_constraints_postSwitch": 2, + "scale_to_effective_capabilities": True, + "policy_name": "Naive", + "tclose_overwrite": 1, + "tclose_days_offset_overwrite": 7, + "use_funded_or_actual_staffing": "funded_plus", + "year_cons_availability_switch": 2025, + "cons_availability_postSwitch": "all", + }, + } + ), + + "Worst-case Scenario": + mix_scenarios( + get_parameters_for_status_quo(), + { + "HealthSystem": { + "yearly_HR_scaling_mode": "GDP_growth_fHE_case1", + "year_mode_switch": 2019, + "mode_appt_constraints_postSwitch": 2, + "scale_to_effective_capabilities": True, + "policy_name": "Naive", + "tclose_overwrite": 1, + "tclose_days_offset_overwrite": 7, + "use_funded_or_actual_staffing": "actual", + "year_cons_availability_switch": 2019, + "cons_availability_postSwitch": "all", + }, + } + ), + + "Demand Sensitivity": + mix_scenarios( + get_parameters_for_status_quo(), + { + "HealthSystem": { + "yearly_HR_scaling_mode": "GDP_growth_fHE_case3", + "year_mode_switch": 2019, + "mode_appt_constraints_postSwitch": 2, + "scale_to_effective_capabilities": True, + "policy_name": "Naive", + "tclose_overwrite": 1, + "tclose_days_offset_overwrite": 7, + "use_funded_or_actual_staffing": "actual", + "year_cons_availability_switch": 2019, + "cons_availability_postSwitch": "all", + }, + } + ), + +#Look into doing sensitivity analyses in the model + "Time appointments Sensitivity": + mix_scenarios( + get_parameters_for_status_quo(), + { + "HealthSystem": { + "yearly_HR_scaling_mode": "GDP_growth_FL_case2_const_tot_i", + "year_mode_switch": 2019, + "mode_appt_constraints_postSwitch": 2, + "scale_to_effective_capabilities": True, + "policy_name": "Naive", + "tclose_overwrite": 1, + "tclose_days_offset_overwrite": 7, + "use_funded_or_actual_staffing": "actual", + "year_cons_availability_switch": 2019, + "cons_availability_postSwitch": "all", + }, + } + ), + } + +if __name__ == '__main__': + from tlo.cli import scenario_run + + scenario_run([__file__]) From 6617d49e5a984ab71f892e13e240e60af213306a Mon Sep 17 00:00:00 2001 From: PempheroM Date: Thu, 20 Nov 2025 10:14:45 +0200 Subject: [PATCH 2/5] changes_yearmode,hr scaling --- .../nurses_analyses/nurses_scenario_analyses.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/scripts/nurses_analyses/nurses_scenario_analyses.py b/src/scripts/nurses_analyses/nurses_scenario_analyses.py index b61e5fa44d..472726bc59 100644 --- a/src/scripts/nurses_analyses/nurses_scenario_analyses.py +++ b/src/scripts/nurses_analyses/nurses_scenario_analyses.py @@ -61,7 +61,7 @@ def _get_scenarios(self) -> Dict[str, Dict]: { "HealthSystem": { "ResourceFile_HR_scaling_by_level_and_officer_type": "default", - "year_mode_switch": 2025, + "year_mode_switch": 2020, "mode_appt_constraints_postSwitch": 2, "scale_to_effective_capabilities": True, "policy_name": "Naive", @@ -80,7 +80,7 @@ def _get_scenarios(self) -> Dict[str, Dict]: { "HealthSystem": { "ResourceFile_HR_scaling_by_level_and_officer_type": "default", - "year_mode_switch": 2025, + "year_mode_switch": 2020, "mode_appt_constraints_postSwitch": 2, "scale_to_effective_capabilities": True, "policy_name": "Naive", @@ -98,8 +98,8 @@ def _get_scenarios(self) -> Dict[str, Dict]: get_parameters_for_status_quo(), { "HealthSystem": { - "yearly_HR_scaling_mode": "GDP_growth_fHE_case1", - "year_mode_switch": 2019, + "yearly_HR_scaling_mode": "historical_scaling", + "year_mode_switch": 2020, "mode_appt_constraints_postSwitch": 2, "scale_to_effective_capabilities": True, "policy_name": "Naive", @@ -117,8 +117,8 @@ def _get_scenarios(self) -> Dict[str, Dict]: get_parameters_for_status_quo(), { "HealthSystem": { - "yearly_HR_scaling_mode": "GDP_growth_fHE_case3", - "year_mode_switch": 2019, + "yearly_HR_scaling_mode": "historical_scaling", + "year_mode_switch": 2020, "mode_appt_constraints_postSwitch": 2, "scale_to_effective_capabilities": True, "policy_name": "Naive", @@ -137,8 +137,8 @@ def _get_scenarios(self) -> Dict[str, Dict]: get_parameters_for_status_quo(), { "HealthSystem": { - "yearly_HR_scaling_mode": "GDP_growth_FL_case2_const_tot_i", - "year_mode_switch": 2019, + "yearly_HR_scaling_mode": "historical_scaling", + "year_mode_switch": 2020, "mode_appt_constraints_postSwitch": 2, "scale_to_effective_capabilities": True, "policy_name": "Naive", From f586244c9b5221efa9c97e20ca92172377f3e764 Mon Sep 17 00:00:00 2001 From: PempheroM Date: Fri, 21 Nov 2025 13:25:09 +0200 Subject: [PATCH 3/5] added default function --- .../nurses_scenario_analyses.py | 82 ++++++++++++++++++- 1 file changed, 81 insertions(+), 1 deletion(-) diff --git a/src/scripts/nurses_analyses/nurses_scenario_analyses.py b/src/scripts/nurses_analyses/nurses_scenario_analyses.py index 472726bc59..6353f0ff38 100644 --- a/src/scripts/nurses_analyses/nurses_scenario_analyses.py +++ b/src/scripts/nurses_analyses/nurses_scenario_analyses.py @@ -45,6 +45,86 @@ def log_configuration(self): def modules(self): return fullmodel(resourcefilepath=self.resources) + [ImprovedHealthSystemAndCareSeekingScenarioSwitcher(resourcefilepath=self.resources)] + def _default_of_all_scenarios(self) -> Dict: + return mix_scenarios( + get_parameters_for_status_quo(), + { + 'HealthSystem': { + 'mode_appt_constraints': 1, + 'mode_appt_constraints_postSwitch': 2, + "scale_to_effective_capabilities": True, + # This happens in the year before mode change, as the model calibration is done by that year + "year_mode_switch": 2020, + 'cons_availability': 'default', + 'cons_availability_postSwitch': "all", + # 'year_cons_availability_switch': 2025, + 'HR_budget_growth_rate': self.hr_budget[0], + 'yearly_HR_scaling_mode': 'historical_scaling', # for 5 years of 2020-2024; source data year 2019 + 'start_year_HR_expansion_by_officer_type': self.YEAR_OF_HRH_EXPANSION, + 'end_year_HR_expansion_by_officer_type': self.end_date.year, + "policy_name": 'Naive', + "tclose_overwrite": 1, + "tclose_days_offset_overwrite": 7, + }, + 'ImprovedHealthSystemAndCareSeekingScenarioSwitcher': { + 'max_healthcare_seeking': [False, False], + 'max_healthsystem_function': self.hs_function[0], + 'year_of_switch': self.YEAR_OF_HRH_EXPANSION, + } + }, + ) + + def _baseline_scenario(self) -> Dict: + return mix_scenarios( + self._default_of_all_scenarios(), + { + 'HealthSystem': { + 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", + 'mode_appt_constraints_postSwitch': 2, + "use_funded_or_actual_staffing": "actual", + }, + }, + ) + + def _improved_staffing_scenario(self) -> Dict: + return mix_scenarios( + self._default_of_all_scenarios(), + { + 'HealthSystem': { + 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", + 'mode_appt_constraints_postSwitch': 2, + "use_funded_or_actual_staffing": "funded_plus", + }, + }, + ) + + def _worst_case_scenario(self) -> Dict: + return mix_scenarios( + self._default_of_all_scenarios(), + { + 'HealthSystem': { + 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", + 'mode_appt_constraints_postSwitch': 2, + "use_funded_or_actual_staffing": "actual", + }, + }, + ) + # To be sensitivity analysis + # def _baseline_scenario(self) -> Dict: + # return mix_scenarios( + # self._default_of_all_scenarios(), + # { + # 'HealthSystem': { + # 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", + # 'year_mode_switch': 2020, + # 'mode_appt_constraints_postSwitch': 2, + # 'scale_to_effective_capabilities': True, + # "use_funded_or_actual_staffing": "actual", + # "year_cons_availability_switch": 2025, + # "cons_availability_postSwitch": "all", + # }, + # }, + # ) def draw_parameters(self, draw_number, rng): if draw_number < self.number_of_draws: return list(self._scenarios.values())[draw_number] @@ -57,7 +137,7 @@ def _get_scenarios(self) -> Dict[str, Dict]: return { "Baseline": mix_scenarios( - get_parameters_for_status_quo(), + self._default_of_all_scenarios(), { "HealthSystem": { "ResourceFile_HR_scaling_by_level_and_officer_type": "default", From 94a9b35069c449ea80a31705de779a44038bf9a9 Mon Sep 17 00:00:00 2001 From: PempheroM Date: Fri, 21 Nov 2025 14:07:32 +0200 Subject: [PATCH 4/5] reformat --- .../nurses_scenario_analyses.py | 241 +++++++----------- 1 file changed, 95 insertions(+), 146 deletions(-) diff --git a/src/scripts/nurses_analyses/nurses_scenario_analyses.py b/src/scripts/nurses_analyses/nurses_scenario_analyses.py index 6353f0ff38..12ab9b3173 100644 --- a/src/scripts/nurses_analyses/nurses_scenario_analyses.py +++ b/src/scripts/nurses_analyses/nurses_scenario_analyses.py @@ -22,12 +22,14 @@ class StaffingScenario(BaseScenario): def __init__(self): super().__init__() - self.seed=12 - self.start_date=Date(2010, 1, 1) - self.end_date=Date(2030, 1, 1) - self.initial_population_size=200 - self.number_of_draws=2 - self.runs_per_draw=2 + self.seed = 0 + self.start_date = Date(2010, 1, 1) + self.end_date = Date(2030, 1, 1) + self.initial_population_size = 200 + self._scenarios = self._get_scenarios() + self.number_of_draws = len(self._scenarios) + self.number_of_draws = 2 + self.runs_per_draw = 2 def log_configuration(self): return { @@ -43,7 +45,14 @@ def log_configuration(self): } def modules(self): - return fullmodel(resourcefilepath=self.resources) + [ImprovedHealthSystemAndCareSeekingScenarioSwitcher(resourcefilepath=self.resources)] + return fullmodel(resourcefilepath=self.resources) + [ + ImprovedHealthSystemAndCareSeekingScenarioSwitcher(resourcefilepath=self.resources)] + + def draw_parameters(self, draw_number, rng): + if draw_number < self.number_of_draws: + return list(self._scenarios.values())[draw_number] + else: + return def _default_of_all_scenarios(self) -> Dict: return mix_scenarios( @@ -57,7 +66,7 @@ def _default_of_all_scenarios(self) -> Dict: "year_mode_switch": 2020, 'cons_availability': 'default', 'cons_availability_postSwitch': "all", - # 'year_cons_availability_switch': 2025, + # 'year_cons_availability_switch': 2025, 'HR_budget_growth_rate': self.hr_budget[0], 'yearly_HR_scaling_mode': 'historical_scaling', # for 5 years of 2020-2024; source data year 2019 'start_year_HR_expansion_by_officer_type': self.YEAR_OF_HRH_EXPANSION, @@ -74,41 +83,86 @@ def _default_of_all_scenarios(self) -> Dict: }, ) - def _baseline_scenario(self) -> Dict: - return mix_scenarios( - self._default_of_all_scenarios(), - { - 'HealthSystem': { - 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", - 'mode_appt_constraints_postSwitch': 2, - "use_funded_or_actual_staffing": "actual", - }, - }, - ) + # def _baseline_scenario(self) -> Dict: + # return mix_scenarios( + # self._default_of_all_scenarios(), + # { + # 'HealthSystem': { + # 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", + # 'mode_appt_constraints_postSwitch': 2, + # "use_funded_or_actual_staffing": "actual", + # }, + # }, + # ) + # + # def _improved_staffing_scenario(self) -> Dict: + # return mix_scenarios( + # self._default_of_all_scenarios(), + # { + # 'HealthSystem': { + # 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", + # 'mode_appt_constraints_postSwitch': 2, + # "use_funded_or_actual_staffing": "funded_plus", + # }, + # }, + # ) + # + # def _worst_case_scenario(self) -> Dict: + # return mix_scenarios( + # self._default_of_all_scenarios(), + # { + # 'HealthSystem': { + # 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", + # 'mode_appt_constraints_postSwitch': 2, + # "use_funded_or_actual_staffing": "actual", + # }, + # }, + # ) + #################################################################### + def _get_scenarios(self) -> Dict[str, Dict]: + """Return the Dict with values for the parameters that are changed, keyed by a name for the scenario. + """ + return { + "Baseline": + mix_scenarios( + self._default_of_all_scenarios(), + { + "HealthSystem": { + 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", + 'mode_appt_constraints_postSwitch': 2, + "use_funded_or_actual_staffing": "actual", + }, + } + ), + + "Improved Staffing": + mix_scenarios( + self._default_of_all_scenarios(), + { + "HealthSystem": { + 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", + 'mode_appt_constraints_postSwitch': 2, + "use_funded_or_actual_staffing": "funded_plus", + }, + } + ), + + "Worse Case": + mix_scenarios( + self._default_of_all_scenarios(), + { + "HealthSystem": { + 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", + 'mode_appt_constraints_postSwitch': 2, + "use_funded_or_actual_staffing": "actual", + }, + } + ), + } + + - def _improved_staffing_scenario(self) -> Dict: - return mix_scenarios( - self._default_of_all_scenarios(), - { - 'HealthSystem': { - 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", - 'mode_appt_constraints_postSwitch': 2, - "use_funded_or_actual_staffing": "funded_plus", - }, - }, - ) - def _worst_case_scenario(self) -> Dict: - return mix_scenarios( - self._default_of_all_scenarios(), - { - 'HealthSystem': { - 'ResourceFile_HR_scaling_by_level_and_officer_type': "historical_scaling", - 'mode_appt_constraints_postSwitch': 2, - "use_funded_or_actual_staffing": "actual", - }, - }, - ) # To be sensitivity analysis # def _baseline_scenario(self) -> Dict: # return mix_scenarios( @@ -125,112 +179,7 @@ def _worst_case_scenario(self) -> Dict: # }, # }, # ) - def draw_parameters(self, draw_number, rng): - if draw_number < self.number_of_draws: - return list(self._scenarios.values())[draw_number] - else: - return - def _get_scenarios(self) -> Dict[str, Dict]: - """Return the Dict with values for the parameters that are changed, keyed by a name for the scenario. - """ - return { - "Baseline": - mix_scenarios( - self._default_of_all_scenarios(), - { - "HealthSystem": { - "ResourceFile_HR_scaling_by_level_and_officer_type": "default", - "year_mode_switch": 2020, - "mode_appt_constraints_postSwitch": 2, - "scale_to_effective_capabilities": True, - "policy_name": "Naive", - "tclose_overwrite": 1, - "tclose_days_offset_overwrite": 7, - "use_funded_or_actual_staffing": "actual", - "year_cons_availability_switch": 2025, - "cons_availability_postSwitch": "all", - }, - } - ), - - "Improved Staffing": - mix_scenarios( - get_parameters_for_status_quo(), - { - "HealthSystem": { - "ResourceFile_HR_scaling_by_level_and_officer_type": "default", - "year_mode_switch": 2020, - "mode_appt_constraints_postSwitch": 2, - "scale_to_effective_capabilities": True, - "policy_name": "Naive", - "tclose_overwrite": 1, - "tclose_days_offset_overwrite": 7, - "use_funded_or_actual_staffing": "funded_plus", - "year_cons_availability_switch": 2025, - "cons_availability_postSwitch": "all", - }, - } - ), - - "Worst-case Scenario": - mix_scenarios( - get_parameters_for_status_quo(), - { - "HealthSystem": { - "yearly_HR_scaling_mode": "historical_scaling", - "year_mode_switch": 2020, - "mode_appt_constraints_postSwitch": 2, - "scale_to_effective_capabilities": True, - "policy_name": "Naive", - "tclose_overwrite": 1, - "tclose_days_offset_overwrite": 7, - "use_funded_or_actual_staffing": "actual", - "year_cons_availability_switch": 2019, - "cons_availability_postSwitch": "all", - }, - } - ), - - "Demand Sensitivity": - mix_scenarios( - get_parameters_for_status_quo(), - { - "HealthSystem": { - "yearly_HR_scaling_mode": "historical_scaling", - "year_mode_switch": 2020, - "mode_appt_constraints_postSwitch": 2, - "scale_to_effective_capabilities": True, - "policy_name": "Naive", - "tclose_overwrite": 1, - "tclose_days_offset_overwrite": 7, - "use_funded_or_actual_staffing": "actual", - "year_cons_availability_switch": 2019, - "cons_availability_postSwitch": "all", - }, - } - ), - -#Look into doing sensitivity analyses in the model - "Time appointments Sensitivity": - mix_scenarios( - get_parameters_for_status_quo(), - { - "HealthSystem": { - "yearly_HR_scaling_mode": "historical_scaling", - "year_mode_switch": 2020, - "mode_appt_constraints_postSwitch": 2, - "scale_to_effective_capabilities": True, - "policy_name": "Naive", - "tclose_overwrite": 1, - "tclose_days_offset_overwrite": 7, - "use_funded_or_actual_staffing": "actual", - "year_cons_availability_switch": 2019, - "cons_availability_postSwitch": "all", - }, - } - ), - } if __name__ == '__main__': from tlo.cli import scenario_run From ec05b17d3ccbf98d07a29992c056452087ef3742 Mon Sep 17 00:00:00 2001 From: PempheroM Date: Fri, 21 Nov 2025 14:15:20 +0200 Subject: [PATCH 5/5] remove unused input --- src/scripts/nurses_analyses/nurses_scenario_analyses.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/scripts/nurses_analyses/nurses_scenario_analyses.py b/src/scripts/nurses_analyses/nurses_scenario_analyses.py index 12ab9b3173..60705d2f60 100644 --- a/src/scripts/nurses_analyses/nurses_scenario_analyses.py +++ b/src/scripts/nurses_analyses/nurses_scenario_analyses.py @@ -10,8 +10,6 @@ from pathlib import Path from typing import Dict -import pandas as pd - from tlo import Date, logging from tlo.analysis.utils import get_parameters_for_status_quo, mix_scenarios from tlo.methods.fullmodel import fullmodel