From b6460a672d254aaed4ab4ed7927c48d9d389c5e6 Mon Sep 17 00:00:00 2001 From: vahid-ahmadi Date: Sat, 24 Oct 2026 14:06:03 +0100 Subject: [PATCH 1/2] Re-source the rail ridership index to ORR and project rail usage (#1862) ORR Table 1220 passenger journeys to 2025-26, held flat after, and rail_usage grown by journeys per person. Co-Authored-By: Claude Opus 5.5 --- changelog.d/1862-rail-ridership-orr.fixed.md | 1 + policyengine_uk/data/uprating_indices.yaml | 4 + .../gov/dft/rail/ridership_index.yaml | 46 ++++---- .../gov/economic_assumptions/yoy_growth.yaml | 29 +++++ .../code_health/test_uprating_declarations.py | 5 - .../tests/test_projection_horizon.py | 12 +- .../tests/test_rail_usage_uprating.py | 110 ++++++++++++++++++ .../tests/test_uprating_inputs_reach_data.py | 7 +- .../variables/gov/dft/rail_usage.py | 7 +- 9 files changed, 180 insertions(+), 41 deletions(-) create mode 100644 changelog.d/1862-rail-ridership-orr.fixed.md create mode 100644 policyengine_uk/tests/test_rail_usage_uprating.py diff --git a/changelog.d/1862-rail-ridership-orr.fixed.md b/changelog.d/1862-rail-ridership-orr.fixed.md new file mode 100644 index 0000000000..e06d7aba17 --- /dev/null +++ b/changelog.d/1862-rail-ridership-orr.fixed.md @@ -0,0 +1 @@ +Correct `gov.dft.rail.ridership_index` to ORR Table 1220 passenger journeys (2021-22 is 2.55 and 2024-25 4.46 on a 2020-21 base, not 0.40 and 0.95), hold it flat from 2026-27, and project `rail_usage` by its growth per person, which it was not projected by before. diff --git a/policyengine_uk/data/uprating_indices.yaml b/policyengine_uk/data/uprating_indices.yaml index 17f363f2cd..b643f87956 100644 --- a/policyengine_uk/data/uprating_indices.yaml +++ b/policyengine_uk/data/uprating_indices.yaml @@ -160,5 +160,9 @@ gov.economic_assumptions.yoy_growth.obr.private_pension_index: - private_pension_income gov.economic_assumptions.yoy_growth.obr.lagged_average_earnings: - housing_service_charges +# ORR passenger journeys per person, as household weights grow by population +# (#1862). +gov.economic_assumptions.yoy_growth.orr.per_capita.rail_passenger_journeys: +- rail_usage gov.economic_assumptions.yoy_growth.ons.population: - household_weight diff --git a/policyengine_uk/parameters/gov/dft/rail/ridership_index.yaml b/policyengine_uk/parameters/gov/dft/rail/ridership_index.yaml index 770667eed9..b4b81fffde 100644 --- a/policyengine_uk/parameters/gov/dft/rail/ridership_index.yaml +++ b/policyengine_uk/parameters/gov/dft/rail/ridership_index.yaml @@ -1,30 +1,26 @@ -description: Cumulative index of rail ridership (passenger journeys). Base year 2020 = 1.0. Used for uprating the quantity component of rail subsidy spending. +description: Index of passenger journeys on Great Britain's railways. Financial year 2020-21 = 1.0. +# ORR Table 1220, passenger journeys in millions (published 18 June 2026), +# divided by the 2020-21 figure, 387.885m, rounded to 4 decimals. The +# financial year starting in April of year t is stored at t-01-01. ORR marks +# every year from 2020-21 provisional. +# +# No official body forecasts passenger journeys, so from 2026-27 the index is +# held at its 2025-26 level: journeys are assumed to stay flat. +# +# Dataset projection grows rail_usage by this index per household +# (gov.economic_assumptions.yoy_growth.orr.per_capita.rail_passenger_journeys), +# as household weights already grow by ons.population (#1862). values: - # Base year (COVID-affected) - 2020-01-01: 1.000 - # 2021: Recovery year - 2021-01-01: 0.400 - # 2022: Continued recovery - 2022-01-01: 0.700 - # 2023: Near full recovery - 2023-01-01: 0.900 - # 2024: Full recovery plus growth - 2024-01-01: 0.950 - # 2025: +1.9% growth (ORR/Steer projected trend) - 2025-01-01: 0.968 - # 2026: +1.9% growth - 2026-01-01: 0.986 - # 2027: +1.9% growth - 2027-01-01: 1.005 - # 2028: +1.9% growth - 2028-01-01: 1.024 - # 2029: +1.9% growth - 2029-01-01: 1.044 + 2020-01-01: 1.0 # 387.885m + 2021-01-01: 2.5525 # 990.062 / 387.885 + 2022-01-01: 3.5700 # 1,384.752 / 387.885 + 2023-01-01: 4.1559 # 1,612.028 / 387.885 + 2024-01-01: 4.4567 # 1,728.704 / 387.885 + 2025-01-01: 4.7165 # 1,829.468 / 387.885 + 2026-01-01: 4.7165 # held flat (assumption) metadata: unit: /1 label: Rail ridership index reference: - - title: ORR Passenger Rail Usage Statistics - href: https://dataportal.orr.gov.uk/statistics/usage/passenger-rail-usage/ - - title: Steer - UK Rail Demand Forecasting - href: https://www.steergroup.com/ + - title: ORR, Table 1220 Passenger journeys, Great Britain, January 1872 to March 2026 (18 June 2026) + href: https://dataportal.orr.gov.uk/statistics/usage/passenger-rail-usage/table-1220-passenger-journeys/ diff --git a/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml b/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml index 1f0380a3fb..c20e23992d 100644 --- a/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml +++ b/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml @@ -1570,6 +1570,35 @@ finance_ni: - title: ONS, Country and regional public sector finances methodology guide, sections 29 and 30 href: https://www.ons.gov.uk/economy/governmentpublicsectorandtaxes/publicsectorfinance/methodologies/countryandregionalpublicsectorfinancesmethodologyguide +orr: + per_capita: + rail_passenger_journeys: + description: Growth in rail passenger journeys per person. + # Growth in gov.dft.rail.ridership_index (ORR Table 1220 passenger + # journeys, Great Britain) net of ons.population growth: + # (index_t / index_t-1) / (1 + population_t) - 1, rounded to 4 + # decimals. Household weights grow by ons.population, so growing each + # household's rail_usage by this keeps the weighted total on ORR's + # journeys (#1862). The financial year starting in April of year t is + # stored at t-01-01. From 2026 journeys are held flat (no official + # forecast), so this is minus population growth. + values: + 2021-01-01: 1.5423 # 2.5525 / 1.0040 - 1 + 2022-01-01: 0.3857 # (3.5700 / 2.5525) / 1.0093 - 1 + 2023-01-01: 0.1491 # (4.1559 / 3.5700) / 1.0131 - 1 + 2024-01-01: 0.0610 # (4.4567 / 4.1559) / 1.0107 - 1 + 2025-01-01: 0.0507 # (4.7165 / 4.4567) / 1.0072 - 1 + 2026-01-01: -0.0038 # 1 / 1.0038 - 1 + 2027-01-01: -0.0037 # 1 / 1.0037 - 1 + 2028-01-01: -0.0040 # 1 / 1.0040 - 1 + 2029-01-01: -0.0044 # 1 / 1.0044 - 1 + 2030-01-01: -0.0045 # 1 / 1.0045 - 1 + metadata: + unit: /1 + label: Rail passenger journeys growth per person + reference: + - title: ORR, Table 1220 Passenger journeys, Great Britain, January 1872 to March 2026 (18 June 2026) + href: https://dataportal.orr.gov.uk/statistics/usage/passenger-rail-usage/table-1220-passenger-journeys/ carbon_intensity: description: Carbon intensity year-over-year growth (decline). values: diff --git a/policyengine_uk/tests/code_health/test_uprating_declarations.py b/policyengine_uk/tests/code_health/test_uprating_declarations.py index 0317076069..004cdeccc2 100644 --- a/policyengine_uk/tests/code_health/test_uprating_declarations.py +++ b/policyengine_uk/tests/code_health/test_uprating_declarations.py @@ -24,11 +24,6 @@ # Variables that declare an index but are deliberately not in the YAML, with # the reason. Anything else failing this test is a genuine gap. KNOWN_UNUPRATED = { - # gov.dft.rail.ridership_index is a CUMULATIVE index (2020 = 1.0), not a - # year-on-year growth rate, so it cannot go in a yoy_growth list without - # compounding wrongly: doing so uprates rail_usage by ~684% over 2024-27. - # Uprating this needs a yoy series, not a YAML entry (#1862). - "rail_usage", # Reported counterpart of employee_pension_contributions, which IS # uprated; the reported variable feeds imputation rather than results. "employee_pension_contributions_reported", diff --git a/policyengine_uk/tests/test_projection_horizon.py b/policyengine_uk/tests/test_projection_horizon.py index 10b3bb020c..fa5567fdcc 100644 --- a/policyengine_uk/tests/test_projection_horizon.py +++ b/policyengine_uk/tests/test_projection_horizon.py @@ -213,14 +213,12 @@ def test_unmapped_inputs_preserve_legacy_years_and_grow_after_2030(projected): """Previously unmapped columns keep their old values through 2030, then grow by the same cumulative-index ratio Core used beyond that year. Columns uprating_indices.yaml now lists (the debts, ISAs, lodger and - boarder rent, gross receipts, salary sacrifice and service charges), and - water bills, which grow by country, grow every year instead, so they are - no longer fallbacks. + boarder rent, gross receipts, salary sacrifice, service charges and rail + usage), and water bills, which grow by country, grow every year instead, + so they are no longer fallbacks. """ - assert { - "rail_usage", - "employee_pension_contributions_reported", - } <= set(FALLBACK_INDEX_OF) + assert "employee_pension_contributions_reported" in FALLBACK_INDEX_OF + assert "rail_usage" not in FALLBACK_INDEX_OF for year in range(BASE_YEAR + 1, PROJECTION_END_YEAR + 1): for variable, index_name in FALLBACK_INDEX_OF.items(): previous = _table(projected[year - 1], variable)[variable].to_numpy() diff --git a/policyengine_uk/tests/test_rail_usage_uprating.py b/policyengine_uk/tests/test_rail_usage_uprating.py new file mode 100644 index 0000000000..d229f05184 --- /dev/null +++ b/policyengine_uk/tests/test_rail_usage_uprating.py @@ -0,0 +1,110 @@ +"""Rail usage is projected by ORR passenger journeys per person (#1862). + +ORR Table 1220 (published 18 June 2026) gives passenger journeys on Great +Britain's railways in millions, by financial year. gov.dft.rail.ridership_index +divides them by the 2020-21 figure. No official body forecasts journeys, so +from 2026-27 they are held flat. + +Household weights grow by ons.population, so each household's rail_usage +grows by journeys growth net of population growth. Growing it by journeys +growth itself would count population growth twice. +""" + +import numpy as np +import pandas as pd +import pytest + +from policyengine_uk import parameters +from policyengine_uk.data import UKSingleYearDataset +from policyengine_uk.data.economic_assumptions import extend_single_year_dataset + +RIDERSHIP = parameters.gov.dft.rail.ridership_index +POPULATION = parameters.gov.economic_assumptions.yoy_growth.ons.population +PER_PERSON = ( + "gov.economic_assumptions.yoy_growth.orr.per_capita.rail_passenger_journeys" +) + +# ORR Table 1220, passenger journeys (million), by the year each financial +# year starts in (2020 is April 2020 to March 2021). +JOURNEYS = { + 2020: 387.885467, + 2021: 990.062033, + 2022: 1_384.752483, + 2023: 1_612.028410, + 2024: 1_728.704107, + 2025: 1_829.468293, +} + + +@pytest.mark.parametrize("year", sorted(JOURNEYS)) +def test_ridership_index_is_orr_journeys_on_a_2020_21_base(year): + # 2021-22: 990.062 / 387.885 = 2.5525; 2024-25: 1,728.704 / 387.885 = + # 4.4567. Before #1862 these were 0.400 and 0.950. + expected = JOURNEYS[year] / JOURNEYS[2020] + assert RIDERSHIP(f"{year}-01-01") == pytest.approx(expected, abs=5e-5) + + +@pytest.mark.parametrize("year", range(2026, 2031)) +def test_journeys_are_held_flat_after_2025_26(year): + assert RIDERSHIP(f"{year}-01-01") == RIDERSHIP("2025-01-01") + + +@pytest.mark.parametrize("year", range(2021, 2031)) +def test_per_person_growth_is_journeys_growth_net_of_population_growth(year): + # 2025-26: (1,829.468 / 1,728.704) / 1.0072 - 1 = 0.0507. Fails if either + # input series changes without re-deriving this one. + instant, previous = f"{year}-01-01", f"{year - 1}-01-01" + expected = RIDERSHIP(instant) / RIDERSHIP(previous) / (1 + POPULATION(instant)) - 1 + assert parameters.get_child(PER_PERSON)(instant) == pytest.approx( + expected, abs=5e-5 + ) + + +def _dataset(rail_usage): + ids = np.arange(len(rail_usage)) + return UKSingleYearDataset( + person=pd.DataFrame( + { + "person_id": ids, + "person_benunit_id": ids, + "person_household_id": ids, + "age": 40, + } + ), + benunit=pd.DataFrame({"benunit_id": ids}), + household=pd.DataFrame( + { + "household_id": ids, + "household_weight": 1.0, + "region": "LONDON", + "tenure_type": "OWNED_OUTRIGHT", + "rent": 0.0, + "council_tax": 0.0, + "rail_usage": rail_usage, + } + ), + fiscal_year=2024, + ) + + +def _total(dataset): + household = dataset.household + return (household["rail_usage"] * household["household_weight"]).sum() + + +def test_projected_rail_usage_tracks_orr_journeys(): + # Each household's usage grows by 1.0583 / 1.0072 = 1.0507 into 2025-26 + # and the weights by 1.0072, so the weighted total grows by ORR's 5.83%. + # From 2026-27 the total stays flat. Before #1862 rail_usage was not + # projected at all. + projected = extend_single_year_dataset( + _dataset([100.0, 40.0]), parameters, end_year=2027 + ) + datasets = projected.datasets + assert datasets[2025].household["rail_usage"].tolist() == pytest.approx( + [105.07, 42.028], rel=1e-4 + ) + assert _total(datasets[2025]) / _total(datasets[2024]) == pytest.approx( + JOURNEYS[2025] / JOURNEYS[2024], abs=1e-4 + ) + assert _total(datasets[2027]) == pytest.approx(_total(datasets[2025]), rel=1e-4) diff --git a/policyengine_uk/tests/test_uprating_inputs_reach_data.py b/policyengine_uk/tests/test_uprating_inputs_reach_data.py index d92df6c5af..5a1ab4abe4 100644 --- a/policyengine_uk/tests/test_uprating_inputs_reach_data.py +++ b/policyengine_uk/tests/test_uprating_inputs_reach_data.py @@ -46,6 +46,9 @@ from policyengine_uk.utils.scenario import Scenario AVERAGE_EARNINGS = "gov.economic_assumptions.yoy_growth.obr.average_earnings" +# The index employee_pension_contributions_reported declares. The YAML does +# not list that variable, so only the post-2030 fallback reads its index. +UNMAPPED_INDEX = "gov.economic_assumptions.indices.obr.average_earnings" PERSONAL_ALLOWANCE = "gov.hmrc.income_tax.allowances.personal_allowance.amount" HF_URL = "hf://policyengine/policyengine-uk-data-private/mock_frs_2025_26.h5" GCS_URL = "gs://policyengine-uk-data-private/mock_frs_2025_26.h5" @@ -394,7 +397,7 @@ def recording_node_at(self, instant): covered = uprating_input_paths() # The unmapped indices, read past the legacy horizon, are in the set. - assert "gov.dft.rail.ridership_index" in read + assert UNMAPPED_INDEX in read for name in read: assert any(name == path or name.startswith(f"{path}.") for path in covered), ( name @@ -403,7 +406,7 @@ def recording_node_at(self, instant): def test_the_read_set_names_inputs_outside_the_growth_rates(): paths = uprating_input_paths() - assert "gov.dft.rail.ridership_index" in paths + assert UNMAPPED_INDEX in paths assert economic_assumptions.CGT_INDEX_PATH in paths assert AVERAGE_EARNINGS in paths diff --git a/policyengine_uk/variables/gov/dft/rail_usage.py b/policyengine_uk/variables/gov/dft/rail_usage.py index da344bcefb..c8e1f75f37 100644 --- a/policyengine_uk/variables/gov/dft/rail_usage.py +++ b/policyengine_uk/variables/gov/dft/rail_usage.py @@ -7,10 +7,13 @@ class rail_usage(Variable): "Rail usage quantity in base year (2020) price terms. " "Should be provided by policyengine-uk-data, derived from " "rail_subsidy_spending / fare_index at survey year. " - "Uprated by rail ridership growth trends (~1.9% annually)." + "Projected by ORR passenger journeys per person, held flat from " + "2026-27." ) entity = Household definition_period = YEAR value_type = float unit = GBP - uprating = "gov.dft.rail.ridership_index" + # Per person, because household weights already grow by population; the + # growth is that of gov.dft.rail.ridership_index (#1862). + uprating = "gov.economic_assumptions.indices.orr.per_capita.rail_passenger_journeys" From 5f74f93af2188b03c8befdd265ae699d74ec260d Mon Sep 17 00:00:00 2001 From: vahid-ahmadi Date: Sun, 25 Oct 2026 12:32:18 +0000 Subject: [PATCH 2/2] Re-derive per-person rail journeys growth to 2073 on the March 2026 population main's ons.population is now the March 2026 vintage (#2611), so every year of orr.per_capita.rail_passenger_journeys is re-derived from it. The offset now runs to 2073, as far as ons.population does, so total journeys stay flat after 2030 (review A1). Tests check every population year and a flat weighted total to PROJECTION_END_YEAR. Co-Authored-By: Claude Opus 5.5 --- .../gov/economic_assumptions/yoy_growth.yaml | 67 ++++++++++++++++--- .../tests/test_rail_usage_uprating.py | 39 ++++++++--- 2 files changed, 85 insertions(+), 21 deletions(-) diff --git a/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml b/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml index 3fdc11e396..5bb37c791a 100644 --- a/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml +++ b/policyengine_uk/parameters/gov/economic_assumptions/yoy_growth.yaml @@ -1593,18 +1593,63 @@ orr: # household's rail_usage by this keeps the weighted total on ORR's # journeys (#1862). The financial year starting in April of year t is # stored at t-01-01. From 2026 journeys are held flat (no official - # forecast), so this is minus population growth. + # forecast), so this is minus population growth, for every year + # ons.population has a value. Re-derive it whenever either series + # changes (policyengine_uk/tests/test_rail_usage_uprating.py checks). values: - 2021-01-01: 1.5423 # 2.5525 / 1.0040 - 1 - 2022-01-01: 0.3857 # (3.5700 / 2.5525) / 1.0093 - 1 - 2023-01-01: 0.1491 # (4.1559 / 3.5700) / 1.0131 - 1 - 2024-01-01: 0.0610 # (4.4567 / 4.1559) / 1.0107 - 1 - 2025-01-01: 0.0507 # (4.7165 / 4.4567) / 1.0072 - 1 - 2026-01-01: -0.0038 # 1 / 1.0038 - 1 - 2027-01-01: -0.0037 # 1 / 1.0037 - 1 - 2028-01-01: -0.0040 # 1 / 1.0040 - 1 - 2029-01-01: -0.0044 # 1 / 1.0044 - 1 - 2030-01-01: -0.0045 # 1 / 1.0045 - 1 + 2021-01-01: 1.5433 # 2.5525 / 1.0036 - 1 + 2022-01-01: 0.3851 # (3.5700 / 2.5525) / 1.0098 - 1 + 2023-01-01: 0.1490 # (4.1559 / 3.5700) / 1.0132 - 1 + 2024-01-01: 0.0607 # (4.4567 / 4.1559) / 1.0110 - 1 + 2025-01-01: 0.0551 # (4.7165 / 4.4567) / 1.0030 - 1 + 2026-01-01: -0.0035 # 1 / 1.0035 - 1 + 2027-01-01: -0.0029 # 1 / 1.0029 - 1 + 2028-01-01: -0.0032 # 1 / 1.0032 - 1 + 2029-01-01: -0.0034 # 1 / 1.0034 - 1 + 2030-01-01: -0.0036 # 1 / 1.0036 - 1 + 2031-01-01: -0.0024 # 1 / 1.0024 - 1 + 2032-01-01: -0.0022 # 1 / 1.0022 - 1 + 2033-01-01: -0.0021 # 1 / 1.0021 - 1 + 2034-01-01: -0.0019 # 1 / 1.0019 - 1 + 2035-01-01: -0.0018 # 1 / 1.0018 - 1 + 2036-01-01: -0.0017 # 1 / 1.0017 - 1 + 2037-01-01: -0.0016 # 1 / 1.0016 - 1 + 2038-01-01: -0.0015 # 1 / 1.0015 - 1 + 2039-01-01: -0.0015 # 1 / 1.0015 - 1 + 2040-01-01: -0.0014 # 1 / 1.0014 - 1 + 2041-01-01: -0.0014 # 1 / 1.0014 - 1 + 2042-01-01: -0.0013 # 1 / 1.0013 - 1 + 2043-01-01: -0.0013 # 1 / 1.0013 - 1 + 2044-01-01: -0.0012 # 1 / 1.0012 - 1 + 2045-01-01: -0.0011 # 1 / 1.0011 - 1 + 2046-01-01: -0.0011 # 1 / 1.0011 - 1 + 2047-01-01: -0.0010 # 1 / 1.0010 - 1 + 2048-01-01: -0.0009 # 1 / 1.0009 - 1 + 2049-01-01: -0.0008 # 1 / 1.0008 - 1 + 2050-01-01: -0.0006 # 1 / 1.0006 - 1 + 2051-01-01: -0.0005 # 1 / 1.0005 - 1 + 2052-01-01: -0.0003 # 1 / 1.0003 - 1 + 2053-01-01: -0.0002 # 1 / 1.0002 - 1 + 2054-01-01: 0.0000 # 1 / 1.0000 - 1 + 2055-01-01: 0.0001 # 1 / 0.9999 - 1 + 2056-01-01: 0.0003 # 1 / 0.9997 - 1 + 2057-01-01: 0.0004 # 1 / 0.9996 - 1 + 2058-01-01: 0.0005 # 1 / 0.9995 - 1 + 2059-01-01: 0.0006 # 1 / 0.9994 - 1 + 2060-01-01: 0.0007 # 1 / 0.9993 - 1 + 2061-01-01: 0.0008 # 1 / 0.9992 - 1 + 2062-01-01: 0.0008 # 1 / 0.9992 - 1 + 2063-01-01: 0.0008 # 1 / 0.9992 - 1 + 2064-01-01: 0.0009 # 1 / 0.9991 - 1 + 2065-01-01: 0.0009 # 1 / 0.9991 - 1 + 2066-01-01: 0.0009 # 1 / 0.9991 - 1 + 2067-01-01: 0.0009 # 1 / 0.9991 - 1 + 2068-01-01: 0.0009 # 1 / 0.9991 - 1 + 2069-01-01: 0.0009 # 1 / 0.9991 - 1 + 2070-01-01: 0.0009 # 1 / 0.9991 - 1 + 2071-01-01: 0.0010 # 1 / 0.9990 - 1 + 2072-01-01: 0.0010 # 1 / 0.9990 - 1 + 2073-01-01: 0.0010 # 1 / 0.9990 - 1 metadata: unit: /1 label: Rail passenger journeys growth per person diff --git a/policyengine_uk/tests/test_rail_usage_uprating.py b/policyengine_uk/tests/test_rail_usage_uprating.py index d229f05184..4c8e83ec03 100644 --- a/policyengine_uk/tests/test_rail_usage_uprating.py +++ b/policyengine_uk/tests/test_rail_usage_uprating.py @@ -16,7 +16,10 @@ from policyengine_uk import parameters from policyengine_uk.data import UKSingleYearDataset -from policyengine_uk.data.economic_assumptions import extend_single_year_dataset +from policyengine_uk.data.economic_assumptions import ( + PROJECTION_END_YEAR, + extend_single_year_dataset, +) RIDERSHIP = parameters.gov.dft.rail.ridership_index POPULATION = parameters.gov.economic_assumptions.yoy_growth.ons.population @@ -49,9 +52,19 @@ def test_journeys_are_held_flat_after_2025_26(year): assert RIDERSHIP(f"{year}-01-01") == RIDERSHIP("2025-01-01") -@pytest.mark.parametrize("year", range(2021, 2031)) +# Every year ons.population has a value for from 2021, so the offset runs as +# far as the population series does and never stops while population growth +# carries on changing. +POPULATION_YEARS = [ + int(value.instant_str[:4]) + for value in POPULATION.values_list + if int(value.instant_str[:4]) >= 2021 +] + + +@pytest.mark.parametrize("year", POPULATION_YEARS) def test_per_person_growth_is_journeys_growth_net_of_population_growth(year): - # 2025-26: (1,829.468 / 1,728.704) / 1.0072 - 1 = 0.0507. Fails if either + # 2025-26: (1,829.468 / 1,728.704) / 1.0030 - 1 = 0.0551. Fails if either # input series changes without re-deriving this one. instant, previous = f"{year}-01-01", f"{year - 1}-01-01" expected = RIDERSHIP(instant) / RIDERSHIP(previous) / (1 + POPULATION(instant)) - 1 @@ -93,18 +106,24 @@ def _total(dataset): def test_projected_rail_usage_tracks_orr_journeys(): - # Each household's usage grows by 1.0583 / 1.0072 = 1.0507 into 2025-26 - # and the weights by 1.0072, so the weighted total grows by ORR's 5.83%. - # From 2026-27 the total stays flat. Before #1862 rail_usage was not - # projected at all. + # Each household's usage grows by 1.0583 / 1.0030 = 1.0551 into 2025-26 + # and the weights by 1.0030, so the weighted total grows by ORR's 5.83%. + # From 2026-27 the total stays flat in every projected year, past the end + # of the OBR forecast too. Before #1862 rail_usage was not projected at + # all. projected = extend_single_year_dataset( - _dataset([100.0, 40.0]), parameters, end_year=2027 + _dataset([100.0, 40.0]), parameters, end_year=PROJECTION_END_YEAR ) datasets = projected.datasets assert datasets[2025].household["rail_usage"].tolist() == pytest.approx( - [105.07, 42.028], rel=1e-4 + [105.51, 42.204], rel=1e-4 ) assert _total(datasets[2025]) / _total(datasets[2024]) == pytest.approx( JOURNEYS[2025] / JOURNEYS[2024], abs=1e-4 ) - assert _total(datasets[2027]) == pytest.approx(_total(datasets[2025]), rel=1e-4) + # Four-decimal rounding of the two growth series moves the total by at + # most 0.005% a year. + for year in range(2026, PROJECTION_END_YEAR + 1): + assert _total(datasets[year]) == pytest.approx( + _total(datasets[2025]), rel=5e-5 * (year - 2025) + ), year