From 2d41fd671b810a5e8d4c7d55620503f78b04d5b9 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 27 Nov 2025 12:49:57 -0500 Subject: [PATCH 1/3] Add rail_usage variable derived from rail_subsidy_spending MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Derives rail_usage (quantity at base year prices) from rail_subsidy_spending by dividing by the fare index for the survey year (2021): rail_usage = rail_subsidy_spending / 1.010 This enables policyengine-uk to properly decompose rail spending into price × quantity, allowing reforms to modify the fare_index parameter independently of usage quantity. Related: PolicyEngine/policyengine-uk#1406 Fixes: #226 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .../datasets/imputations/services/services.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/policyengine_uk_data/datasets/imputations/services/services.py b/policyengine_uk_data/datasets/imputations/services/services.py index 6d76525eb..656801fb2 100644 --- a/policyengine_uk_data/datasets/imputations/services/services.py +++ b/policyengine_uk_data/datasets/imputations/services/services.py @@ -9,6 +9,12 @@ from .nhs import impute_nhs_usage from .etb import impute_public_services, create_efrs_input_dataset +# Rail fare index for the survey year (2021, from ETB data) +# This is the cumulative fare index from base year 2020 +# Used to derive rail_usage (quantity) from rail_subsidy_spending +# See: policyengine-uk gov.dft.rail.fare_index parameter +RAIL_FARE_INDEX_SURVEY_YEAR = 1.010 # 2021 value (+1.0% from 2020) + def impute_services( dataset: UKSingleYearDataset, @@ -44,6 +50,13 @@ def impute_services( .values ) + # Derive rail_usage (quantity at base year prices) from rail_subsidy_spending + # rail_usage = rail_subsidy_spending / fare_index + # This allows reforms to modify fare_index independently of usage quantity + dataset.household["rail_usage"] = ( + dataset.household["rail_subsidy_spending"] / RAIL_FARE_INDEX_SURVEY_YEAR + ) + visit_variables = [ "a_and_e_visits", "admitted_patient_visits", From 460966e26ddab1c7c7362f16ac62920f94d6a473 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 27 Nov 2025 12:52:13 -0500 Subject: [PATCH 2/3] Add changelog entry and format code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- changelog_entry.yaml | 4 ++++ .../datasets/imputations/services/services.py | 3 ++- uv.lock | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/changelog_entry.yaml b/changelog_entry.yaml index e69de29bb..497b79bda 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -0,0 +1,4 @@ +- bump: minor + changes: + added: + - rail_usage variable derived from rail_subsidy_spending for price/quantity decomposition diff --git a/policyengine_uk_data/datasets/imputations/services/services.py b/policyengine_uk_data/datasets/imputations/services/services.py index 656801fb2..9e1e97c79 100644 --- a/policyengine_uk_data/datasets/imputations/services/services.py +++ b/policyengine_uk_data/datasets/imputations/services/services.py @@ -54,7 +54,8 @@ def impute_services( # rail_usage = rail_subsidy_spending / fare_index # This allows reforms to modify fare_index independently of usage quantity dataset.household["rail_usage"] = ( - dataset.household["rail_subsidy_spending"] / RAIL_FARE_INDEX_SURVEY_YEAR + dataset.household["rail_subsidy_spending"] + / RAIL_FARE_INDEX_SURVEY_YEAR ) visit_variables = [ diff --git a/uv.lock b/uv.lock index e331e8442..f8fd52f58 100644 --- a/uv.lock +++ b/uv.lock @@ -1409,7 +1409,7 @@ wheels = [ [[package]] name = "policyengine-uk-data" -version = "1.21.0" +version = "1.23.3" source = { editable = "." } dependencies = [ { name = "black" }, From d4d2d6879da6b0a00c04cc730056933dfc2b942f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 27 Nov 2025 13:04:14 -0500 Subject: [PATCH 3/3] Pull fare_index from policyengine-uk parameters with fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Import system from policyengine_uk to access parameters - Add get_fare_index_survey_year() function that tries to read gov.dft.rail.fare_index from parameters, falls back to 1.010 - This allows the code to work both before and after policyengine-uk PR #1406 is merged 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- changelog_entry.yaml | 2 +- .../datasets/imputations/services/services.py | 32 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/changelog_entry.yaml b/changelog_entry.yaml index 497b79bda..ed1216bec 100644 --- a/changelog_entry.yaml +++ b/changelog_entry.yaml @@ -1,4 +1,4 @@ - bump: minor changes: added: - - rail_usage variable derived from rail_subsidy_spending for price/quantity decomposition + - rail_usage variable derived from rail_subsidy_spending / fare_index at survey year, enabling fare reforms to modify prices independently of usage quantity diff --git a/policyengine_uk_data/datasets/imputations/services/services.py b/policyengine_uk_data/datasets/imputations/services/services.py index 9e1e97c79..22717e076 100644 --- a/policyengine_uk_data/datasets/imputations/services/services.py +++ b/policyengine_uk_data/datasets/imputations/services/services.py @@ -6,14 +6,30 @@ """ from policyengine_uk.data import UKSingleYearDataset +from policyengine_uk.system import system from .nhs import impute_nhs_usage from .etb import impute_public_services, create_efrs_input_dataset -# Rail fare index for the survey year (2021, from ETB data) -# This is the cumulative fare index from base year 2020 -# Used to derive rail_usage (quantity) from rail_subsidy_spending -# See: policyengine-uk gov.dft.rail.fare_index parameter -RAIL_FARE_INDEX_SURVEY_YEAR = 1.010 # 2021 value (+1.0% from 2020) +# ETB survey year (most recent year in ETB data) +ETB_SURVEY_YEAR = 2021 + +# Fallback fare index for 2021 if parameter not yet available in policyengine-uk +# This is the cumulative fare index from base year 2020 (+1.0% from 2020) +FALLBACK_FARE_INDEX_2021 = 1.010 + + +def get_fare_index_survey_year() -> float: + """ + Get the rail fare index for the ETB survey year. + + Attempts to read from policyengine-uk parameters, falls back to + hardcoded value if parameter not yet available. + """ + try: + return system.parameters.gov.dft.rail.fare_index(ETB_SURVEY_YEAR) + except AttributeError: + # Parameter not yet available in policyengine-uk + return FALLBACK_FARE_INDEX_2021 def impute_services( @@ -51,11 +67,11 @@ def impute_services( ) # Derive rail_usage (quantity at base year prices) from rail_subsidy_spending - # rail_usage = rail_subsidy_spending / fare_index + # rail_usage = rail_subsidy_spending / fare_index at survey year # This allows reforms to modify fare_index independently of usage quantity + fare_index_survey_year = get_fare_index_survey_year() dataset.household["rail_usage"] = ( - dataset.household["rail_subsidy_spending"] - / RAIL_FARE_INDEX_SURVEY_YEAR + dataset.household["rail_subsidy_spending"] / fare_index_survey_year ) visit_variables = [