From d8aae1b129ad8f1b475a88cd75b54258bf32389e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 22 Oct 2026 01:01:19 -0400 Subject: [PATCH 1/5] Uprate benefits by the previous September's CPI Most benefits rise each April under the Social Security Administration Act 1992 s150 review, which measures prices by the CPI 12-month rate for the previous September (every Up-rating Order EM since April 2011; SI 2026/148 EM paras 6.4-6.5; HCWS1101). gov.benefit_uprating_cpi followed calendar-year CPI instead; it now follows gov.economic_assumptions.indices.september_cpi_uprating. The UC standard allowance and health element indices, built from it, follow September CPI as the Universal Credit Act 2025 requires. September 2026 CPI is 3.5% (ONS D7G7, 21 October 2026), so CPI-linked benefits rise 3.5% in April 2027 rather than 2.3%. Amounts outside the s150 prices review that were on the index keep their lagged calendar-year CPI projection, with the evidence in each file: the PC standard minimum guarantee (s150A) and savings credit threshold, the HB pension-age allowances, DfE funding rates and the council tax high value surcharge. IIDB moves from same-year CPI to the benefit index. Tests: property tests of the rate and index; every parameter on the index rises by September CPI after its last published value; a differential test that 22 CPI-linked series' published April rates from 2011 are the previous rate times the model's September CPI rise, rounded to 5p; triple lock tests updated for September 2026 being published; one HB test made explicit that its claimant does not claim UC (by 2030 a little UC would passport them). Fixes #2533. Co-Authored-By: Claude Opus 5.5 --- .../benefit-uprating-september-cpi.changed.md | 1 + docs/book/assumptions/growthfactors.md | 12 + .../parameters/gov/benefit_uprating_cpi.yaml | 12 +- .../gov/dfe/childcare_funding_rate.yaml | 6 +- .../parameters/gov/dwp/IIDB/maximum.yaml | 3 +- .../allowances/couple/aged.yaml | 7 +- .../allowances/couple/aged_from_cutoff.yaml | 8 +- .../allowances/lone_parent/aged.yaml | 7 +- .../lone_parent/aged_from_cutoff.yaml | 8 +- .../allowances/single/aged.yaml | 7 +- .../allowances/single/aged_from_cutoff.yaml | 8 +- .../guarantee_credit/minimum_guarantee.yaml | 14 +- .../savings_credit/threshold.yaml | 6 +- .../gov/economic_assumptions/README.md | 13 + .../cpi_september.yaml | 4 +- .../high_value_surcharge/amount.yaml | 6 +- ..._or_temporary_accommodation_disregard.yaml | 3 + .../tests/published_benefit_rates.py | 821 ++++++++++++++++++ .../tests/test_benefit_uprating_published.py | 131 +++ .../tests/test_september_cpi_uprating.py | 281 ++++++ .../test_september_cpi_uprating_properties.py | 188 ++++ .../test_triple_lock_statutory_inputs.py | 16 +- 22 files changed, 1538 insertions(+), 24 deletions(-) create mode 100644 changelog.d/benefit-uprating-september-cpi.changed.md create mode 100644 policyengine_uk/tests/published_benefit_rates.py create mode 100644 policyengine_uk/tests/test_benefit_uprating_published.py create mode 100644 policyengine_uk/tests/test_september_cpi_uprating.py create mode 100644 policyengine_uk/tests/test_september_cpi_uprating_properties.py diff --git a/changelog.d/benefit-uprating-september-cpi.changed.md b/changelog.d/benefit-uprating-september-cpi.changed.md new file mode 100644 index 0000000000..b911c8e3ae --- /dev/null +++ b/changelog.d/benefit-uprating-september-cpi.changed.md @@ -0,0 +1 @@ +Uprate benefits each April by the previous September's CPI, the measure every Social Security Administration Act 1992 s150 review uses, instead of the previous calendar year's CPI (#2533). `gov.benefit_uprating_cpi` now follows `gov.economic_assumptions.indices.september_cpi_uprating`, so the Universal Credit standard allowance and health element indices built on it follow September CPI too, as the Universal Credit Act 2025 requires. Add September 2026 CPI (3.5%, ONS 21 October 2026), which sets the April 2027 rise. IIDB moves from same-year calendar CPI to the benefit index. Amounts outside the s150 prices review that were on the index (Pension Credit standard minimum guarantee and savings credit threshold, Housing Benefit pension-age allowances, DfE funding rates, council tax high value surcharge) keep their previous projection. diff --git a/docs/book/assumptions/growthfactors.md b/docs/book/assumptions/growthfactors.md index 3e8b4e1700..5ef08906a1 100644 --- a/docs/book/assumptions/growthfactors.md +++ b/docs/book/assumptions/growthfactors.md @@ -34,6 +34,18 @@ We apply this to: `diesel_spending`, `petrol_spending` The pump prices that turn spending into litres (`household.consumption.fuel.prices.petrol` and `.diesel`) are DESNZ calendar-year averages (Quarterly Energy Prices tables 4.1.1 and 4.1.2), the same basis Microcosm UK uses to price household fuel spending. The spending growth rates (`petrol_spending_litre_proxy`, `diesel_spending_litre_proxy`) are derived from those prices, so that weighted litres follow the volume path above: (1 + growth) = (1 + volume growth) × pricet / pricet−1 ÷ (1 + population growth). Whenever the prices change, re-derive the growth rates; a test checks that they match. +## Benefit uprating (September CPI) + +Most benefit rates rise each April by the CPI 12-month rate for the +September before, as in the annual review under Social Security +Administration Act 1992 s150 ([written statement HCWS1101](https://questions-statements.parliament.uk/written-statements/detail/2025-11-26/hcws1101): +3.8% for April 2026). Published September figures come from ONS series D7G7 +(3.5% for September 2026, which sets April 2027); later years use the OBR's +September CPI forecast and, after the EFO horizon, calendar-year CPI. Each +figure is rounded to 0.1 percentage points, and a fall in prices leaves rates +unchanged. Parameters with `uprating: gov.benefit_uprating_cpi` rise by these +rates after their last published value. + ## Average earnings ```{note} diff --git a/policyengine_uk/parameters/gov/benefit_uprating_cpi.yaml b/policyengine_uk/parameters/gov/benefit_uprating_cpi.yaml index ddf58c5e83..ca10f05851 100644 --- a/policyengine_uk/parameters/gov/benefit_uprating_cpi.yaml +++ b/policyengine_uk/parameters/gov/benefit_uprating_cpi.yaml @@ -1,7 +1,15 @@ -description: Most recent September CPIH index value, updated for each uprating occurrence (2005=100). +description: Index of the April prices uprating of benefits, which rises each April by the previous September's CPI inflation. Only ratios between dates are meaningful. values: 2005-01-01: 100 2010-01-01: 114.48 metadata: label: Benefit uprating index - uprating: gov.economic_assumptions.indices.obr.lagged_cpi + # April Y uses September Y-1 CPI; see create_september_cpi_uprating.py. + uprating: gov.economic_assumptions.indices.september_cpi_uprating + reference: + - title: Social Security Administration Act 1992, s150 (annual up-rating of benefits) + href: https://www.legislation.gov.uk/ukpga/1992/5/section/150 + - title: Statutory Review of State Pension and Benefit Rates 2026/27, written statement HCWS1101 (26 November 2025) + href: https://questions-statements.parliament.uk/written-statements/detail/2025-11-26/hcws1101 + - title: Explanatory Memorandum to the Social Security Benefits Up-rating Order 2026 (SI 2026/148), paras 6.4-6.5 (CPI in the year to September 2025) + href: https://www.legislation.gov.uk/uksi/2026/148/pdfs/uksiem_20260148_en_001.pdf diff --git a/policyengine_uk/parameters/gov/dfe/childcare_funding_rate.yaml b/policyengine_uk/parameters/gov/dfe/childcare_funding_rate.yaml index 7d8ccf8ff8..be60686981 100644 --- a/policyengine_uk/parameters/gov/dfe/childcare_funding_rate.yaml +++ b/policyengine_uk/parameters/gov/dfe/childcare_funding_rate.yaml @@ -7,7 +7,11 @@ metadata: threshold_unit: year amount_unit: currency-GBP label: Childcare entitlement hourly rates by age - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: DfE funding rates are not amounts in the + # Social Security Administration Act 1992 s150 review. Keeps the lagged + # calendar-year CPI projection it had through gov.benefit_uprating_cpi until + # its own basis is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi reference: - title: House of Commons Library | Early years funding in England href: https://commonslibrary.parliament.uk/research-briefings/cbp-8052/ diff --git a/policyengine_uk/parameters/gov/dwp/IIDB/maximum.yaml b/policyengine_uk/parameters/gov/dwp/IIDB/maximum.yaml index 428284e488..c3ed0b7a99 100644 --- a/policyengine_uk/parameters/gov/dwp/IIDB/maximum.yaml +++ b/policyengine_uk/parameters/gov/dwp/IIDB/maximum.yaml @@ -18,7 +18,8 @@ metadata: - title: Benefit and pension rates 2015 to 2016 (DWP, gov.uk) href: https://www.gov.uk/government/publications/proposed-benefit-and-pension-rates-2015-to-2016 unit: currency-GBP - uprating: gov.economic_assumptions.indices.obr.consumer_price_index + # An Up-rating Order amount (Social Security Administration Act 1992 s150). + uprating: gov.benefit_uprating_cpi values: # 2015-16 to 2026-27: DWP Benefit and pension rates (gov.uk), each # year's rate as listed in that year's publication. diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged.yaml index a32bf5a7b0..69333ab432 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged.yaml @@ -15,7 +15,12 @@ metadata: period: week unit: currency-GBP label: Housing Benefit couple pension-age personal allowance - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: the pension-age personal allowances rose 4.75% + # in April 2026 (£244.40 to £256.00 single), when September CPI was 3.8%, and + # 2.3% in April 2017, when it was 1.0% (Benefit and pension rates 2026 to + # 2027, 2017 to 2018). Keeps the lagged calendar-year CPI projection it had + # through gov.benefit_uprating_cpi until its own basis is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi reference: - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 (SI 2006/214), Schedule 3, paragraph 1(2)(b) href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged_from_cutoff.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged_from_cutoff.yaml index 9a36d00ec6..01ccc8d258 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged_from_cutoff.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged_from_cutoff.yaml @@ -10,7 +10,13 @@ metadata: period: week unit: currency-GBP label: Housing Benefit couple personal allowance, State Pension age reached from April 2021 - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: the post-April 2021 pension-age personal + # allowances equal the Pension Credit standard minimum guarantee in every year + # from 2021-22 to 2026-27, which is reviewed against earnings under Social + # Security Administration Act 1992 s150A(1)(d). Keeps the lagged calendar-year + # CPI projection it had through gov.benefit_uprating_cpi until its own basis + # is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi reference: - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) (Amendment) Regulations 2021 (SI 2021/188), regulation 2(3), inserting paragraph 1(2)(c) of Schedule 3 to SI 2006/214 from 1 April 2021 href: https://www.legislation.gov.uk/uksi/2021/188/regulation/2/made diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged.yaml index 4f1a89f61f..ec9e5906ab 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged.yaml @@ -16,7 +16,12 @@ metadata: period: week unit: currency-GBP label: Housing Benefit lone parent pension-age personal allowance - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: the pension-age personal allowances rose 4.75% + # in April 2026 (£244.40 to £256.00 single), when September CPI was 3.8%, and + # 2.3% in April 2017, when it was 1.0% (Benefit and pension rates 2026 to + # 2027, 2017 to 2018). Keeps the lagged calendar-year CPI projection it had + # through gov.benefit_uprating_cpi until its own basis is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi reference: - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 (SI 2006/214), Schedule 3, paragraph 1(1)(b) href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged_from_cutoff.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged_from_cutoff.yaml index 17e5e79a24..f8e9cc9d6d 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged_from_cutoff.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged_from_cutoff.yaml @@ -10,7 +10,13 @@ metadata: period: week unit: currency-GBP label: Housing Benefit lone parent personal allowance, State Pension age reached from April 2021 - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: the post-April 2021 pension-age personal + # allowances equal the Pension Credit standard minimum guarantee in every year + # from 2021-22 to 2026-27, which is reviewed against earnings under Social + # Security Administration Act 1992 s150A(1)(d). Keeps the lagged calendar-year + # CPI projection it had through gov.benefit_uprating_cpi until its own basis + # is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi reference: - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) (Amendment) Regulations 2021 (SI 2021/188), regulation 2(3), inserting paragraph 1(1)(c) of Schedule 3 to SI 2006/214 from 1 April 2021 href: https://www.legislation.gov.uk/uksi/2021/188/regulation/2/made diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged.yaml index 4fdbd68a0a..5392ebe49a 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged.yaml @@ -15,7 +15,12 @@ metadata: period: week unit: currency-GBP label: Housing Benefit single pension-age personal allowance - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: the pension-age personal allowances rose 4.75% + # in April 2026 (£244.40 to £256.00 single), when September CPI was 3.8%, and + # 2.3% in April 2017, when it was 1.0% (Benefit and pension rates 2026 to + # 2027, 2017 to 2018). Keeps the lagged calendar-year CPI projection it had + # through gov.benefit_uprating_cpi until its own basis is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi reference: - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 (SI 2006/214), Schedule 3, paragraph 1(1)(b) href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged_from_cutoff.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged_from_cutoff.yaml index 5c446ef80a..a996b8e945 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged_from_cutoff.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged_from_cutoff.yaml @@ -10,7 +10,13 @@ metadata: period: week unit: currency-GBP label: Housing Benefit single personal allowance, State Pension age reached from April 2021 - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: the post-April 2021 pension-age personal + # allowances equal the Pension Credit standard minimum guarantee in every year + # from 2021-22 to 2026-27, which is reviewed against earnings under Social + # Security Administration Act 1992 s150A(1)(d). Keeps the lagged calendar-year + # CPI projection it had through gov.benefit_uprating_cpi until its own basis + # is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi reference: - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) (Amendment) Regulations 2021 (SI 2021/188), regulation 2(3), inserting paragraph 1(1)(c) of Schedule 3 to SI 2006/214 from 1 April 2021 href: https://www.legislation.gov.uk/uksi/2021/188/regulation/2/made diff --git a/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee.yaml b/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee.yaml index a1dfb5f57b..d4bdb1e6ee 100644 --- a/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee.yaml +++ b/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee.yaml @@ -14,7 +14,12 @@ SINGLE: 2025-04-01: 227.1 2026-04-01: 238 metadata: - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: the standard minimum guarantee is reviewed + # against earnings under Social Security Administration Act 1992 s150A(1)(d) + # (4.8% in April 2026, written statement HCWS1101). Keeps the lagged + # calendar-year CPI projection it had through gov.benefit_uprating_cpi until + # its own basis is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi COUPLE: values: 2015-04-06: 230.85 @@ -30,7 +35,12 @@ COUPLE: 2025-04-01: 346.6 2026-04-01: 363.25 metadata: - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: the standard minimum guarantee is reviewed + # against earnings under Social Security Administration Act 1992 s150A(1)(d) + # (4.8% in April 2026, written statement HCWS1101). Keeps the lagged + # calendar-year CPI projection it had through gov.benefit_uprating_cpi until + # its own basis is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi metadata: period: week label: Pension Credit minimum guarantee diff --git a/policyengine_uk/parameters/gov/dwp/pension_credit/savings_credit/threshold.yaml b/policyengine_uk/parameters/gov/dwp/pension_credit/savings_credit/threshold.yaml index 568efeac00..e2d831518e 100644 --- a/policyengine_uk/parameters/gov/dwp/pension_credit/savings_credit/threshold.yaml +++ b/policyengine_uk/parameters/gov/dwp/pension_credit/savings_credit/threshold.yaml @@ -34,7 +34,11 @@ COUPLE: metadata: unit: currency-GBP period: week - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: the threshold rose 4.9% in April 2026, when + # September CPI was 3.8% (Benefit and pension rates 2026 to 2027). Keeps the + # lagged calendar-year CPI projection it had through gov.benefit_uprating_cpi + # until its own basis is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi propagate_metadata_to_children: true reference: - title: The State Pension Credit Regulations 2002 reg. 7 diff --git a/policyengine_uk/parameters/gov/economic_assumptions/README.md b/policyengine_uk/parameters/gov/economic_assumptions/README.md index 9cbaae3c9f..e760936032 100644 --- a/policyengine_uk/parameters/gov/economic_assumptions/README.md +++ b/policyengine_uk/parameters/gov/economic_assumptions/README.md @@ -188,6 +188,19 @@ rise. `create_economic_assumption_indices` compounds it into September figure, so a scenario that edits calendar-year CPI or sets September CPI directly moves both. +Most benefits rise each April by this measure: the Secretary of State's +annual review under Social Security Administration Act 1992 s150 measures +prices by the CPI 12-month rate for the previous September (every Up-rating +Order Explanatory Memorandum since April 2011; 3.8% for April 2026, written +statement HCWS1101). `gov.benefit_uprating_cpi`, which the benefit rate +parameters name as their `uprating`, follows `indices.september_cpi_uprating`, +and so do the Universal Credit standard allowance and health element indices +built from it. Amounts outside the s150 prices review that sat on +`gov.benefit_uprating_cpi` (the Pension Credit standard minimum guarantee and +savings credit threshold, Housing Benefit pension-age allowances, DfE funding +rates and the council tax high value surcharge) keep the lagged calendar-year +CPI projection they had, through `indices.obr.lagged_cpi`. + Income Tax Act 2007 ss21 and 57 index the basic rate limit and the personal allowance by this measure once no freeze applies. [`../hmrc/create_threshold_indexation.py`](../hmrc/create_threshold_indexation.py) diff --git a/policyengine_uk/parameters/gov/economic_assumptions/statutory_uprating_inputs/cpi_september.yaml b/policyengine_uk/parameters/gov/economic_assumptions/statutory_uprating_inputs/cpi_september.yaml index 9925e29396..fa3c5cdc20 100644 --- a/policyengine_uk/parameters/gov/economic_assumptions/statutory_uprating_inputs/cpi_september.yaml +++ b/policyengine_uk/parameters/gov/economic_assumptions/statutory_uprating_inputs/cpi_september.yaml @@ -20,9 +20,11 @@ values: 2023-09-01: 0.067 2024-09-01: 0.017 2025-09-01: 0.038 + # ONS CPI release of 21 October 2026. + 2026-09-01: 0.035 # Not yet published. From here the value is calendar-year CPI growth # (yoy_growth.obr.consumer_price_index) plus forecast_gap.cpi_september. - 2026-09-01: null + 2027-09-01: null metadata: unit: /1 label: September CPI inflation diff --git a/policyengine_uk/parameters/gov/hmrc/council_tax/high_value_surcharge/amount.yaml b/policyengine_uk/parameters/gov/hmrc/council_tax/high_value_surcharge/amount.yaml index 643a2ffd76..52b3faeb04 100644 --- a/policyengine_uk/parameters/gov/hmrc/council_tax/high_value_surcharge/amount.yaml +++ b/policyengine_uk/parameters/gov/hmrc/council_tax/high_value_surcharge/amount.yaml @@ -26,7 +26,11 @@ metadata: type: single_amount threshold_unit: currency-GBP amount_unit: currency-GBP - uprating: gov.benefit_uprating_cpi + # Not uprated by September CPI: the surcharge is not an amount in the Social + # Security Administration Act 1992 s150 review. Keeps the lagged calendar-year + # CPI projection it had through gov.benefit_uprating_cpi until its own basis + # is modelled. + uprating: gov.economic_assumptions.indices.obr.lagged_cpi label: High Value Council Tax Surcharge reference: - title: High Value Council Tax Surcharge diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml index d6027e8a89..d10e7a88c9 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml @@ -451,6 +451,9 @@ benunit: members: [person] in_specified_or_temporary_accommodation: true + # By 2030 this person would be entitled to a little Universal Credit, + # which passports the means test (all earnings disregarded). + would_claim_uc: false output: housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 housing_benefit_applicable_income_disregard: 4_301.96 diff --git a/policyengine_uk/tests/published_benefit_rates.py b/policyengine_uk/tests/published_benefit_rates.py new file mode 100644 index 0000000000..81c2e62efe --- /dev/null +++ b/policyengine_uk/tests/published_benefit_rates.py @@ -0,0 +1,821 @@ +"""Published weekly benefit rates, April 2010 to April 2026. + +Rates in force from each April, as published by DWP. April 2013 to April 2026 +come from the "Benefit and pension rates" documents on gov.uk: each document +lists the previous and the new year, so every year from April 2013 to April +2025 appears in two documents, and the two agree. April 2010 to April 2013 +rates for Carer's Allowance, Attendance Allowance and Disability Living +Allowance come from the Social Security Benefits Up-rating Orders. +""" + +# Benefit and pension rates to (and later), keyed by April Y of the new rates. +RATES_DOCUMENTS = { + 2014: "https://assets.publishing.service.gov.uk/media/5a748212ed915d0e8bf18db3/Benefit_and_Pension_rates_2014-15.pdf", + 2015: "https://assets.publishing.service.gov.uk/media/5a7eac8fed915d74e33f1b40/proposed_benefit_and_pension_rates_2015_to_2016.pdf", + 2016: "https://assets.publishing.service.gov.uk/media/5a7f304fed915d74e33f4e0d/proposed_benefit_and_pension_rates_2016_to_2017.pdf", + 2017: "https://assets.publishing.service.gov.uk/media/5a809e8be5274a2e87dbad87/proposed-benefit-and-pension-rates-2017-to-2018.pdf", + 2018: "https://assets.publishing.service.gov.uk/media/5ac4abbae5274a0b107ead7e/proposed-benefit-and-pension-rates-2018-to-2019.pdf", + 2019: "https://assets.publishing.service.gov.uk/media/5ca6100fe5274a77da137ee9/Benefit_and_pension_rates_2019.pdf", + 2020: "https://assets.publishing.service.gov.uk/media/5e8ee784d3bf7f4120cb1874/benefit-and-pension-rates-2020.pdf", + 2021: "https://assets.publishing.service.gov.uk/media/60fe862d8fa8f5043411d861/benefit-and-pension-rates-2021-2022.pdf", + 2022: "https://assets.publishing.service.gov.uk/media/61a0ee27e90e07044b1ef468/benefit-and-pension-rates-2022-2023.pdf", + 2023: "https://assets.publishing.service.gov.uk/media/64956da1831311000c29625c/benefit-and-pensions-rates-2023-2024.pdf", + 2024: "https://assets.publishing.service.gov.uk/media/660d236c38f66c001184a967/benefit-pension-rates-2024-2025.pdf", + 2025: "https://assets.publishing.service.gov.uk/media/67efe453cb0feef57df7e571/benefit_and_pension_rates_2025_to_2026.pdf", + 2026: "https://assets.publishing.service.gov.uk/media/69931706ceeaa48d377f6bd5/Benefit-and-pension-rates-2026-2027.pdf", +} + +# Social Security Benefits Up-rating Orders (made), keyed by April of the new rates. +UP_RATING_ORDERS = { + 2010: "https://www.legislation.gov.uk/uksi/2010/793/made", + 2011: "https://www.legislation.gov.uk/uksi/2011/821/made", + 2012: "https://www.legislation.gov.uk/uksi/2012/780/made", + 2013: "https://www.legislation.gov.uk/uksi/2013/574/made", +} + +# Series: (description, {April: weekly rate in pounds}). +PUBLISHED = { + "carers_allowance": ( + "Carer's Allowance", + { + 2010: 53.90, + 2011: 55.55, + 2012: 58.45, + 2013: 59.75, + 2014: 61.35, + 2015: 62.10, + 2016: 62.10, + 2017: 62.70, + 2018: 64.60, + 2019: 66.15, + 2020: 67.25, + 2021: 67.60, + 2022: 69.70, + 2023: 76.75, + 2024: 81.90, + 2025: 83.30, + 2026: 86.45, + }, + ), + "attendance_allowance_higher": ( + "Attendance Allowance, higher rate", + { + 2010: 71.40, + 2011: 73.60, + 2012: 77.45, + 2013: 79.15, + 2014: 81.30, + 2015: 82.30, + 2016: 82.30, + 2017: 83.10, + 2018: 85.60, + 2019: 87.65, + 2020: 89.15, + 2021: 89.60, + 2022: 92.40, + 2023: 101.75, + 2024: 108.55, + 2025: 110.40, + 2026: 114.60, + }, + ), + "attendance_allowance_lower": ( + "Attendance Allowance, lower rate", + { + 2010: 47.80, + 2011: 49.30, + 2012: 51.85, + 2013: 53.00, + 2014: 54.45, + 2015: 55.10, + 2016: 55.10, + 2017: 55.65, + 2018: 57.30, + 2019: 58.70, + 2020: 59.70, + 2021: 60.00, + 2022: 61.85, + 2023: 68.10, + 2024: 72.65, + 2025: 73.90, + 2026: 76.70, + }, + ), + "dla_care_highest": ( + "DLA care component, highest rate", + { + 2010: 71.40, + 2011: 73.60, + 2012: 77.45, + 2013: 79.15, + 2014: 81.30, + 2015: 82.30, + 2016: 82.30, + 2017: 83.10, + 2018: 85.60, + 2019: 87.65, + 2020: 89.15, + 2021: 89.60, + 2022: 92.40, + 2023: 101.75, + 2024: 108.55, + 2025: 110.40, + 2026: 114.60, + }, + ), + "dla_care_middle": ( + "DLA care component, middle rate", + { + 2010: 47.80, + 2011: 49.30, + 2012: 51.85, + 2013: 53.00, + 2014: 54.45, + 2015: 55.10, + 2016: 55.10, + 2017: 55.65, + 2018: 57.30, + 2019: 58.70, + 2020: 59.70, + 2021: 60.00, + 2022: 61.85, + 2023: 68.10, + 2024: 72.65, + 2025: 73.90, + 2026: 76.70, + }, + ), + "dla_care_lowest": ( + "DLA care component, lowest rate", + { + 2010: 18.95, + 2011: 19.55, + 2012: 20.55, + 2013: 21.00, + 2014: 21.55, + 2015: 21.80, + 2016: 21.80, + 2017: 22.00, + 2018: 22.65, + 2019: 23.20, + 2020: 23.60, + 2021: 23.70, + 2022: 24.45, + 2023: 26.90, + 2024: 28.70, + 2025: 29.20, + 2026: 30.30, + }, + ), + "dla_mobility_higher": ( + "DLA mobility component, higher rate", + { + 2010: 49.85, + 2011: 51.40, + 2012: 54.05, + 2013: 55.25, + 2014: 56.75, + 2015: 57.45, + 2016: 57.45, + 2017: 58.00, + 2018: 59.75, + 2019: 61.20, + 2020: 62.25, + 2021: 62.55, + 2022: 64.50, + 2023: 71.00, + 2024: 75.75, + 2025: 77.05, + 2026: 80.00, + }, + ), + "dla_mobility_lower": ( + "DLA mobility component, lower rate", + { + 2010: 18.95, + 2011: 19.55, + 2012: 20.55, + 2013: 21.00, + 2014: 21.55, + 2015: 21.80, + 2016: 21.80, + 2017: 22.00, + 2018: 22.65, + 2019: 23.20, + 2020: 23.60, + 2021: 23.70, + 2022: 24.45, + 2023: 26.90, + 2024: 28.70, + 2025: 29.20, + 2026: 30.30, + }, + ), + "pip_daily_living_enhanced": ( + "PIP daily living, enhanced rate", + { + 2013: 79.15, + 2014: 81.30, + 2015: 82.30, + 2016: 82.30, + 2017: 83.10, + 2018: 85.60, + 2019: 87.65, + 2020: 89.15, + 2021: 89.60, + 2022: 92.40, + 2023: 101.75, + 2024: 108.55, + 2025: 110.40, + 2026: 114.60, + }, + ), + "pip_daily_living_standard": ( + "PIP daily living, standard rate", + { + 2013: 53.00, + 2014: 54.45, + 2015: 55.10, + 2016: 55.10, + 2017: 55.65, + 2018: 57.30, + 2019: 58.70, + 2020: 59.70, + 2021: 60.00, + 2022: 61.85, + 2023: 68.10, + 2024: 72.65, + 2025: 73.90, + 2026: 76.70, + }, + ), + "pip_mobility_enhanced": ( + "PIP mobility, enhanced rate", + { + 2013: 55.25, + 2014: 56.75, + 2015: 57.45, + 2016: 57.45, + 2017: 58.00, + 2018: 59.75, + 2019: 61.20, + 2020: 62.25, + 2021: 62.55, + 2022: 64.50, + 2023: 71.00, + 2024: 75.75, + 2025: 77.05, + 2026: 80.00, + }, + ), + "pip_mobility_standard": ( + "PIP mobility, standard rate", + { + 2013: 21.00, + 2014: 21.55, + 2015: 21.80, + 2016: 21.80, + 2017: 22.00, + 2018: 22.65, + 2019: 23.20, + 2020: 23.60, + 2021: 23.70, + 2022: 24.45, + 2023: 26.90, + 2024: 28.70, + 2025: 29.20, + 2026: 30.30, + }, + ), + "sda_basic": ( + "Severe Disablement Allowance, basic rate", + { + 2013: 71.80, + 2014: 73.75, + 2015: 74.65, + 2016: 74.65, + 2017: 75.40, + 2018: 77.65, + 2019: 79.50, + 2020: 80.85, + 2021: 81.25, + 2022: 83.75, + 2023: 92.20, + 2024: 98.40, + 2025: 100.05, + 2026: 103.85, + }, + ), + "caa_part_time": ( + "Constant attendance allowance (IIDB), part-time rate", + { + 2013: 32.35, + 2014: 33.20, + 2015: 33.60, + 2016: 33.60, + 2017: 33.95, + 2018: 34.95, + 2019: 35.80, + 2020: 36.40, + 2021: 36.60, + 2022: 37.75, + 2023: 41.55, + 2024: 44.35, + 2025: 45.10, + 2026: 46.80, + }, + ), + "caa_normal_maximum": ( + "Constant attendance allowance (IIDB), normal maximum rate", + { + 2013: 64.70, + 2014: 66.40, + 2015: 67.20, + 2016: 67.20, + 2017: 67.90, + 2018: 69.90, + 2019: 71.60, + 2020: 72.80, + 2021: 73.20, + 2022: 75.50, + 2023: 83.10, + 2024: 88.70, + 2025: 90.20, + 2026: 93.60, + }, + ), + "caa_intermediate": ( + "Constant attendance allowance (IIDB), intermediate rate", + { + 2013: 97.05, + 2014: 99.60, + 2015: 100.80, + 2016: 100.80, + 2017: 101.85, + 2018: 104.85, + 2019: 107.40, + 2020: 109.20, + 2021: 109.80, + 2022: 113.25, + 2023: 124.65, + 2024: 133.05, + 2025: 135.30, + 2026: 140.40, + }, + ), + "caa_exceptional": ( + "Constant attendance allowance (IIDB), exceptional rate", + { + 2013: 129.40, + 2014: 132.80, + 2015: 134.40, + 2016: 134.40, + 2017: 135.80, + 2018: 139.80, + 2019: 143.20, + 2020: 145.60, + 2021: 146.40, + 2022: 151.00, + 2023: 166.20, + 2024: 177.40, + 2025: 180.40, + 2026: 187.20, + }, + ), + "iidb_100": ( + "Industrial Injuries Disablement Benefit, 100% assessment", + { + 2013: 161.60, + 2014: 166.00, + 2015: 168.00, + 2016: 168.00, + 2017: 169.70, + 2018: 174.80, + 2019: 179.00, + 2020: 182.00, + 2021: 182.90, + 2022: 188.60, + 2023: 207.60, + 2024: 221.50, + 2025: 225.30, + 2026: 233.90, + }, + ), + "premium_carer": ( + "Carer premium", + { + 2013: 33.30, + 2014: 34.20, + 2015: 34.60, + 2016: 34.60, + 2017: 34.95, + 2018: 36.00, + 2019: 36.85, + 2020: 37.50, + 2021: 37.70, + 2022: 38.85, + 2023: 42.75, + 2024: 45.60, + 2025: 46.40, + 2026: 48.15, + }, + ), + "premium_disability_single": ( + "Disability premium, single", + { + 2013: 31.00, + 2014: 31.85, + 2015: 32.25, + 2016: 32.25, + 2017: 32.55, + 2018: 33.55, + 2019: 34.35, + 2020: 34.95, + 2021: 35.10, + 2022: 36.20, + 2023: 39.85, + 2024: 42.50, + 2025: 43.20, + 2026: 44.85, + }, + ), + "premium_disability_couple": ( + "Disability premium, couple", + { + 2013: 44.20, + 2014: 45.40, + 2015: 45.95, + 2016: 45.95, + 2017: 46.40, + 2018: 47.80, + 2019: 48.95, + 2020: 49.80, + 2021: 50.05, + 2022: 51.60, + 2023: 56.80, + 2024: 60.60, + 2025: 61.65, + 2026: 64.00, + }, + ), + "premium_enhanced_single": ( + "Enhanced disability premium, single", + { + 2013: 15.15, + 2014: 15.55, + 2015: 15.75, + 2016: 15.75, + 2017: 15.90, + 2018: 16.40, + 2019: 16.80, + 2020: 17.10, + 2021: 17.20, + 2022: 17.75, + 2023: 19.55, + 2024: 20.85, + 2025: 21.20, + 2026: 22.00, + }, + ), + "premium_enhanced_couple": ( + "Enhanced disability premium, couple", + { + 2013: 21.75, + 2014: 22.35, + 2015: 22.60, + 2016: 22.60, + 2017: 22.85, + 2018: 23.55, + 2019: 24.10, + 2020: 24.50, + 2021: 24.60, + 2022: 25.35, + 2023: 27.90, + 2024: 29.75, + 2025: 30.25, + 2026: 31.40, + }, + ), + "premium_severe_single": ( + "Severe disability premium, single", + { + 2013: 59.50, + 2014: 61.10, + 2015: 61.85, + 2016: 61.85, + 2017: 62.45, + 2018: 64.30, + 2019: 65.85, + 2020: 66.95, + 2021: 67.30, + 2022: 69.40, + 2023: 76.40, + 2024: 81.50, + 2025: 82.90, + 2026: 86.05, + }, + ), + "premium_severe_couple_higher": ( + "Severe disability premium, couple (higher rate)", + { + 2013: 119.00, + 2014: 122.20, + 2015: 123.70, + 2016: 123.70, + 2017: 124.90, + 2018: 128.60, + 2019: 131.70, + 2020: 133.90, + 2021: 134.60, + 2022: 138.80, + 2023: 152.80, + 2024: 163.00, + 2025: 165.80, + 2026: 172.10, + }, + ), + "pc_severe_disability_single": ( + "Pension Credit additional amount for severe disability, single", + { + 2013: 59.50, + 2014: 61.10, + 2015: 61.85, + 2016: 61.85, + 2017: 62.45, + 2018: 64.30, + 2019: 65.85, + 2020: 66.95, + 2021: 67.30, + 2022: 69.40, + 2023: 76.40, + 2024: 81.50, + 2025: 82.90, + 2026: 86.05, + }, + ), + "pc_carer": ( + "Pension Credit additional amount for carers", + { + 2013: 33.30, + 2014: 34.20, + 2015: 34.60, + 2016: 34.60, + 2017: 34.95, + 2018: 36.00, + 2019: 36.85, + 2020: 37.50, + 2021: 37.70, + 2022: 38.85, + 2023: 42.75, + 2024: 45.60, + 2025: 46.40, + 2026: 48.15, + }, + ), + "pc_subsequent_children": ( + "Pension Credit child amount, subsequent children", + { + 2018: 53.34, + 2019: 53.34, + 2020: 54.32, + 2021: 54.60, + 2022: 56.35, + 2023: 61.88, + 2024: 66.29, + 2025: 67.42, + 2026: 69.98, + }, + ), + "pc_first_child_before_2017": ( + "Pension Credit child amount, first child born before 6 April 2017", + { + 2018: 63.84, + 2019: 63.84, + 2020: 64.82, + 2021: 65.10, + 2022: 66.85, + 2023: 72.31, + 2024: 76.79, + 2025: 78.10, + 2026: 81.07, + }, + ), + "pc_disabled_child_lower": ( + "Pension Credit disabled child, lower rate", + { + 2018: 29.02, + 2019: 29.02, + 2020: 29.52, + 2021: 29.66, + 2022: 30.58, + 2023: 33.67, + 2024: 35.93, + 2025: 36.54, + 2026: 37.93, + }, + ), + "pc_disabled_child_higher": ( + "Pension Credit disabled child, higher rate", + { + 2018: 88.34, + 2019: 90.23, + 2020: 92.12, + 2021: 92.54, + 2022: 95.48, + 2023: 104.86, + 2024: 112.21, + 2025: 114.12, + 2026: 118.46, + }, + ), + "is_single_under_25": ( + "Income Support personal allowance, single under 25", + { + 2013: 56.80, + 2014: 57.35, + 2015: 57.90, + 2016: 57.90, + 2017: 57.90, + 2018: 57.90, + 2019: 57.90, + 2020: 58.90, + 2021: 59.20, + 2022: 61.05, + 2023: 67.20, + 2024: 71.70, + 2025: 72.90, + 2026: 75.65, + }, + ), + "is_single_25_or_over": ( + "Income Support personal allowance, single 25 or over", + { + 2013: 71.70, + 2014: 72.40, + 2015: 73.10, + 2016: 73.10, + 2017: 73.10, + 2018: 73.10, + 2019: 73.10, + 2020: 74.35, + 2021: 74.70, + 2022: 77.00, + 2023: 84.80, + 2024: 90.50, + 2025: 92.05, + 2026: 95.55, + }, + ), + "is_lone_under_18": ( + "Income Support personal allowance, lone parent under 18", + { + 2013: 56.80, + 2014: 57.35, + 2015: 57.90, + 2016: 57.90, + 2017: 57.90, + 2018: 57.90, + 2019: 57.90, + 2020: 58.90, + 2021: 59.20, + 2022: 61.05, + 2023: 67.20, + 2024: 71.70, + 2025: 72.90, + 2026: 75.65, + }, + ), + "is_lone_18_or_over": ( + "Income Support personal allowance, lone parent 18 or over", + { + 2013: 71.70, + 2014: 72.40, + 2015: 73.10, + 2016: 73.10, + 2017: 73.10, + 2018: 73.10, + 2019: 73.10, + 2020: 74.35, + 2021: 74.70, + 2022: 77.00, + 2023: 84.80, + 2024: 90.50, + 2025: 92.05, + 2026: 95.55, + }, + ), + "is_couple_both_under_18": ( + "Income Support personal allowance, couple both under 18", + { + 2013: 56.80, + 2014: 57.35, + 2015: 57.90, + 2016: 57.90, + 2017: 57.90, + 2018: 57.90, + 2019: 57.90, + 2020: 58.90, + 2021: 59.20, + 2022: 61.05, + 2023: 67.20, + 2024: 71.70, + 2025: 72.90, + 2026: 75.65, + }, + ), + "is_couple_under_18_and_25_plus": ( + "Income Support personal allowance, couple one under 18 and one 25 or over", + { + 2013: 71.70, + 2014: 72.40, + 2015: 73.10, + 2016: 73.10, + 2017: 73.10, + 2018: 73.10, + 2019: 73.10, + 2020: 74.35, + 2021: 74.70, + 2022: 77.00, + 2023: 84.80, + 2024: 90.50, + 2025: 92.05, + 2026: 95.55, + }, + ), + "is_couple_both_18_or_over": ( + "Income Support personal allowance, couple both 18 or over", + { + 2013: 112.55, + 2014: 113.70, + 2015: 114.85, + 2016: 114.85, + 2017: 114.85, + 2018: 114.85, + 2019: 114.85, + 2020: 116.80, + 2021: 117.40, + 2022: 121.05, + 2023: 133.30, + 2024: 142.25, + 2025: 144.65, + 2026: 150.15, + }, + ), + "jsa_ib_under_25": ( + "Income-based JSA personal allowance, under 25", + { + 2013: 56.80, + 2014: 57.35, + 2015: 57.90, + 2016: 57.90, + 2017: 57.90, + 2018: 57.90, + 2019: 57.90, + 2020: 58.90, + 2021: 59.20, + 2022: 61.05, + 2023: 67.20, + 2024: 71.70, + 2025: 72.90, + 2026: 75.65, + }, + ), + "jsa_ib_25_or_over": ( + "Income-based JSA personal allowance, 25 or over", + { + 2013: 71.70, + 2014: 72.40, + 2015: 73.10, + 2016: 73.10, + 2017: 73.10, + 2018: 73.10, + 2019: 73.10, + 2020: 74.35, + 2021: 74.70, + 2022: 77.00, + 2023: 84.80, + 2024: 90.50, + 2025: 92.05, + 2026: 95.55, + }, + ), + "jsa_ib_couple_both_18_or_over": ( + "Income-based JSA personal allowance, couple both 18 or over", + { + 2013: 112.55, + 2014: 113.70, + 2015: 114.85, + 2016: 114.85, + 2017: 114.85, + 2018: 114.85, + 2019: 114.85, + 2020: 116.80, + 2021: 117.40, + 2022: 121.05, + 2023: 133.30, + 2024: 142.25, + 2025: 144.65, + 2026: 150.15, + }, + ), +} diff --git a/policyengine_uk/tests/test_benefit_uprating_published.py b/policyengine_uk/tests/test_benefit_uprating_published.py new file mode 100644 index 0000000000..8e901ab6b4 --- /dev/null +++ b/policyengine_uk/tests/test_benefit_uprating_published.py @@ -0,0 +1,131 @@ +"""Published April upratings against September CPI and the model's rates. + +Two checks against DWP's published weekly rates (published_benefit_rates): + +1. Differential: each April from 2011 to 2026, the published rate of every + CPI-linked series below equals the previous April's rate raised by the + model's September CPI rise and rounded to the nearest 5p. The model's + rise is the one that projects these benefits forward, so this ties the + model's series to what the Secretary of State actually did in 16 reviews. +2. (PolicyEngine/policyengine-uk#1925's test_dwp_published_rates.py checks + that the model's parameters hold the published rates.) + +Series that the review does not raise by September CPI are checked for the +departure the published rates show instead: amounts derived from another +rate (constant attendance allowance, the couple severe disability premium) +and the 1% rises and freeze of working-age personal allowances. +""" + +from decimal import ROUND_HALF_UP, Decimal + +import pytest + +from policyengine_uk.system import system +from policyengine_uk.tests.published_benefit_rates import PUBLISHED + +# Series whose every published April rise is September CPI, rounded to 5p. +SEPTEMBER_CPI_SERIES = [ + "carers_allowance", + "attendance_allowance_higher", + "attendance_allowance_lower", + "dla_care_highest", + "dla_care_middle", + "dla_care_lowest", + "dla_mobility_higher", + "dla_mobility_lower", + "pip_daily_living_enhanced", + "pip_daily_living_standard", + "pip_mobility_enhanced", + "pip_mobility_standard", + "sda_basic", + "caa_part_time", + "premium_carer", + "premium_disability_single", + "premium_disability_couple", + "premium_enhanced_single", + "premium_enhanced_couple", + "premium_severe_single", + "pc_severe_disability_single", + "pc_carer", +] + + +def rates(series): + return PUBLISHED[series][1] + + +def rise(year): + return system.parameters.gov.economic_assumptions.yoy_growth.september_cpi_uprating( + f"{year}-01-01" + ) + + +def to_nearest(amount, step): + """Round to the nearest multiple of step, halves up, as DWP rounds.""" + step = Decimal(step) + units = (Decimal(repr(amount)) / step).quantize(Decimal(1), ROUND_HALF_UP) + return float(units * step) + + +def uprated(previous, year, step="0.05"): + return to_nearest(previous * (1 + rise(year)), step) + + +@pytest.mark.parametrize("series", SEPTEMBER_CPI_SERIES) +def test_published_april_rates_are_the_previous_rate_raised_by_september_cpi(series): + published = rates(series) + years = [year for year in sorted(published) if year - 1 in published] + assert years[0] <= 2014 and years[-1] == 2026 + for year in years: + assert uprated(published[year - 1], year) == published[year], (series, year) + + +def test_carers_allowance_from_april_2011(): + """£53.90 (April 2010) to £86.45 (April 2026), all 16 reviews.""" + published = rates("carers_allowance") + assert min(published) == 2010 + assert len([year for year in published if year > 2010]) == 16 + + +def test_iidb_rises_by_september_cpi_rounded_to_10p(): + published = rates("iidb_100") + for year in sorted(published)[1:]: + assert uprated(published[year - 1], year, "0.1") == published[year], year + + +def test_constant_attendance_allowance_rates_are_multiples_of_the_part_time_rate(): + part_time = rates("caa_part_time") + for series, multiple in [ + ("caa_normal_maximum", 2), + ("caa_intermediate", 3), + ("caa_exceptional", 4), + ]: + for year, value in rates(series).items(): + assert value == pytest.approx(multiple * part_time[year]), (series, year) + + +def test_couple_severe_disability_premium_is_twice_the_single_rate(): + single = rates("premium_severe_single") + for year, value in rates("premium_severe_couple_higher").items(): + assert value == pytest.approx(2 * single[year]), year + + +def test_working_age_personal_allowances_show_the_1_percent_years_and_the_freeze(): + """The published rates rise 1% in April 2014 and 2015 (rounded to 5p), + stay flat in April 2016 to 2019, and follow September CPI otherwise.""" + for series in [ + "is_single_under_25", + "is_single_25_or_over", + "is_couple_both_18_or_over", + "jsa_ib_25_or_over", + ]: + published = rates(series) + for year in sorted(published)[1:]: + previous = published[year - 1] + if year in (2014, 2015): + expected = to_nearest(previous * 1.01, "0.05") + elif year in (2016, 2017, 2018, 2019): + expected = previous + else: + expected = uprated(previous, year) + assert published[year] == expected, (series, year) diff --git a/policyengine_uk/tests/test_september_cpi_uprating.py b/policyengine_uk/tests/test_september_cpi_uprating.py new file mode 100644 index 0000000000..d72c462a40 --- /dev/null +++ b/policyengine_uk/tests/test_september_cpi_uprating.py @@ -0,0 +1,281 @@ +"""The September CPI benefit uprating in the full parameter tree. + +Most benefits rise each April by the previous September's CPI 12-month rate +(Social Security Administration Act 1992 s150; written statement HCWS1101). +These tests check the April rise against September CPI, that every +parameter uprated by ``gov.benefit_uprating_cpi`` follows it after its last +published value, that the State Pension triple lock reads the same September +figure, and that scenarios on the inputs move benefit rates. +""" + +import pytest +from policyengine_core.parameters import Parameter, load_parameter_file + +from policyengine_uk import Simulation +from policyengine_uk.model_api import Scenario +from policyengine_uk.parameters.gov.dwp.state_pension.triple_lock.create_triple_lock import ( + read_uprating_years, +) +from policyengine_uk.parameters.gov.economic_assumptions.create_september_cpi_uprating import ( + FIRST_UPRATING_YEAR, + september_cpi_uprating_rate, +) +from policyengine_uk.parameters.gov.economic_assumptions.create_statutory_uprating_inputs import ( + last_input_year, +) +from policyengine_uk.system import system +from policyengine_uk.tax_benefit_system import COUNTRY_DIR + +INDEX = "gov.benefit_uprating_cpi" +INPUTS = "gov.economic_assumptions.statutory_uprating_inputs" +OBR = "gov.economic_assumptions.yoy_growth.obr" +# create_economic_assumption_indices builds the indices to 2039. +LAST_INDEX_YEAR = 2039 + +# The index is stored to 5 decimal places, which leaves a few hundredths of +# a penny of noise on a weekly rate. +PENNY_TENTH = 1e-3 + +PERSON = { + "people": {"person": {"age": {2026: 40}}}, + "benunits": {"benunit": {"members": ["person"]}}, + "households": {"household": {"members": ["person"]}}, +} + + +def parameters_under(changes): + simulation = Simulation( + situation=PERSON, + scenario=Scenario(parameter_changes=changes, applied_before_data_load=True), + ) + return simulation.tax_benefit_system.parameters + + +def rise(parameters, year): + return parameters.gov.economic_assumptions.yoy_growth.september_cpi_uprating( + f"{year}-01-01" + ) + + +def carers_allowance(parameters, year): + return parameters.gov.dwp.carers_allowance.rate(f"{year}-04-30") + + +def test_each_april_rises_by_the_previous_september_cpi(): + parameters = system.parameters + cpi_september = ( + parameters.gov.economic_assumptions.statutory_uprating_inputs.cpi_september + ) + last_year = last_input_year(parameters) + 1 + assert last_year >= 2074 + for year in range(FIRST_UPRATING_YEAR, last_year + 1): + september = cpi_september(f"{year - 1}-09-01") + assert rise(parameters, year) == september_cpi_uprating_rate(september), year + + +# ONS D7G7, September of the year before each April. +PUBLISHED_APRIL_RISES = { + 2011: 0.031, + 2012: 0.052, + 2013: 0.022, + 2014: 0.027, + 2015: 0.012, + # September 2015 CPI was -0.1%: benefits were held, not cut. + 2016: 0.0, + 2017: 0.010, + 2018: 0.030, + 2019: 0.024, + 2020: 0.017, + 2021: 0.005, + 2022: 0.031, + 2023: 0.101, + 2024: 0.067, + 2025: 0.017, + 2026: 0.038, + # ONS CPI release of 21 October 2026. + 2027: 0.035, +} + + +def test_published_september_rates_give_the_april_rises(): + for year, published in PUBLISHED_APRIL_RISES.items(): + assert rise(system.parameters, year) == published, year + + +def test_april_2028_rise_is_the_obr_september_2027_forecast(): + """OBR March 2026 EFO: calendar-year 2027 CPI plus the receipts Table 3.19 + gap for September 2027 (2.0719%), applied as the published 2.1%.""" + assert rise(system.parameters, 2028) == 0.021 + + +def test_the_index_rises_by_each_april_rise(): + parameters = system.parameters + index = parameters.gov.benefit_uprating_cpi + for year in range(2011, LAST_INDEX_YEAR + 1): + growth = index(f"{year}-04-30") / index(f"{year - 1}-04-30") - 1 + assert growth == pytest.approx(rise(parameters, year), abs=2e-5), year + + +def uprating_parameter(parameter): + uprating = (parameter.metadata or {}).get("uprating") + if isinstance(uprating, dict): + return uprating.get("parameter") + return uprating + + +def uprated_by_the_index(node): + for parameter in node.get_descendants(): + if isinstance(parameter, Parameter) and uprating_parameter(parameter) == INDEX: + yield parameter + + +def last_published_year(raw, name): + """Year of the last value written in the parameter's YAML file.""" + return max(int(value.instant_str[:4]) for value in raw.get_child(name).values_list) + + +def test_every_parameter_on_the_index_rises_by_september_cpi_after_its_last_value(): + """After its last published value, each parameter uprated by the index + rises by exactly the April rise: 0% in April 2016, 2.1% in April 2027.""" + parameters = system.parameters + # The processed tree, including its baseline copy, is uprated, so read + # the YAML files for the last published dates. + raw = load_parameter_file(str(COUNTRY_DIR / "parameters"), name="") + checked = 0 + for parameter in uprated_by_the_index(parameters.gov): + last_year = last_published_year(raw, parameter.name) + years = range(max(last_year + 1, 2016), LAST_INDEX_YEAR + 1) + for year in years: + before = parameter(f"{year - 1}-04-30") + after = parameter(f"{year}-04-30") + if not before: + continue + assert after / before - 1 == pytest.approx( + rise(parameters, year), abs=2e-5 + ), (parameter.name, year) + checked += len(years) > 0 + assert checked >= 50 + + +# Parameters with a start_instant before their last value that an open pull +# request fixes. Add an entry only with that pull request's number. +KNOWN_EARLIER_START = set() + + +def test_no_parameter_projects_from_an_earlier_start(): + """An uprating start_instant before the last value makes policyengine-core + project from the start value, not the last published one, which put a + 12% jump into April 2027 for constant attendance allowance and the + disability premiums.""" + raw = load_parameter_file(str(COUNTRY_DIR / "parameters"), name="") + offenders = [] + for parameter in system.parameters.gov.get_descendants(): + if not isinstance(parameter, Parameter): + continue + uprating = (parameter.metadata or {}).get("uprating") + if not (isinstance(uprating, dict) and "start_instant" in uprating): + continue + last = max( + value.instant_str for value in raw.get_child(parameter.name).values_list + ) + if str(uprating["start_instant"]) < last: + offenders.append(parameter.name) + assert set(offenders) <= KNOWN_EARLIER_START, offenders + + +LAGGED_CPI = "gov.economic_assumptions.indices.obr.lagged_cpi" + + +def test_amounts_outside_the_review_keep_their_lagged_calendar_cpi_projection(): + """Amounts that were on gov.benefit_uprating_cpi but are not raised by + September CPI (the Pension Credit standard minimum guarantee, the Housing + Benefit pension-age allowances, DfE funding rates and others) still rise + by the previous calendar year's CPI after their last value.""" + parameters = system.parameters + raw = load_parameter_file(str(COUNTRY_DIR / "parameters"), name="") + calendar_cpi = ( + parameters.gov.economic_assumptions.yoy_growth.obr.consumer_price_index + ) + names = [] + for parameter in parameters.gov.get_descendants(): + if not isinstance(parameter, Parameter): + continue + if uprating_parameter(parameter) != LAGGED_CPI: + continue + names.append(parameter.name) + last_year = last_published_year(raw, parameter.name) + for year in range(max(last_year + 1, 2016), LAST_INDEX_YEAR + 1): + before = parameter(f"{year - 1}-04-30") + after = parameter(f"{year}-04-30") + if not before: + continue + assert after / before - 1 == pytest.approx( + calendar_cpi(f"{year - 1}-01-01"), abs=2e-5 + ), (parameter.name, year) + assert "gov.dwp.pension_credit.guarantee_credit.minimum_guarantee.SINGLE" in names + assert "gov.dwp.housing_benefit.allowances.single.aged" in names + + +def test_the_triple_lock_reads_the_same_september_cpi(): + """The State Pension's CPI element and the benefit rise read the same + September CPI parameter through the same rounding, so they must agree + every year; this pins the year each reads (a one-year shift fails).""" + parameters = system.parameters + for year, inputs in read_uprating_years(parameters).items(): + assert max(inputs.cpi, 0.0) == rise(parameters, year), year + + +def test_scenario_setting_september_cpi_moves_the_next_april(): + parameters = parameters_under( + {f"{INPUTS}.cpi_september": {"year:2026-09-01:1": 0.05}} + ) + assert rise(parameters, 2027) == 0.05 + assert carers_allowance(parameters, 2027) == pytest.approx( + 86.45 * 1.05, abs=PENNY_TENTH + ) + # Only that April moves. + assert rise(parameters, 2028) == rise(system.parameters, 2028) + assert carers_allowance(parameters, 2026) == 86.45 + + +def test_scenario_on_calendar_cpi_moves_the_forecast_with_the_obr_gap(): + """Calendar-year CPI for 2027 moves the September 2027 forecast one for + one (keeping the OBR's +0.072pp gap), and so the April 2028 rise.""" + parameters = parameters_under( + {f"{OBR}.consumer_price_index": {"year:2027-01-01:1": 0.04}} + ) + assert rise(parameters, 2028) == 0.041 + assert carers_allowance(parameters, 2028) == pytest.approx( + carers_allowance(parameters, 2027) * 1.041, abs=PENNY_TENTH + ) + + +def test_falling_prices_hold_benefits(): + parameters = parameters_under( + {f"{INPUTS}.cpi_september": {"year:2026-09-01:1": -0.01}} + ) + assert rise(parameters, 2027) == 0 + assert carers_allowance(parameters, 2027) == pytest.approx(86.45, abs=PENNY_TENTH) + + +def test_carers_allowance_projection(): + """£86.45 a week from April 2026, then the September CPI rises: 3.5% in + April 2027 (September 2026 CPI).""" + parameters = system.parameters + assert carers_allowance(parameters, 2026) == 86.45 + assert carers_allowance(parameters, 2027) == pytest.approx( + 86.45 * 1.035, abs=PENNY_TENTH + ) + assert carers_allowance(parameters, 2028) == pytest.approx( + 86.45 * 1.035 * (1 + rise(parameters, 2028)), abs=PENNY_TENTH + ) + + +def test_uc_standard_allowance_follows_september_cpi_and_the_act_uplift(): + """Universal Credit Act 2025 s1: the 2026-27 amount (£424.90, single 25 + or over) rises by September 2026 CPI (3.5%) and by the change in the + uplift (2.3% to 3.1%) in April 2027.""" + amount = system.parameters.gov.dwp.universal_credit.standard_allowance.amount + assert amount.SINGLE_OLD("2027-04-30") == pytest.approx( + 424.90 * 1.035 * 1.031 / 1.023, rel=1e-5 + ) diff --git a/policyengine_uk/tests/test_september_cpi_uprating_properties.py b/policyengine_uk/tests/test_september_cpi_uprating_properties.py new file mode 100644 index 0000000000..f9532b534d --- /dev/null +++ b/policyengine_uk/tests/test_september_cpi_uprating_properties.py @@ -0,0 +1,188 @@ +"""Property tests for the September CPI benefit uprating. + +Invariants, for every path of September CPI inputs: + +1. The April rise is the previous September's CPI rounded to the published + 0.1 percentage points, and zero when that is negative: never a cut. +2. The rate never falls when September CPI rises, and a published figure + passes through unchanged. +3. The index starts at 1 in the 2010 base, compounds each April's rise, and + agrees with an independent Decimal compounding of the inputs. +4. The index never falls. +5. Raising any September's CPI never lowers the index, and changes it only + from the following April on. +6. Building the series twice from the same inputs gives the same series. +""" + +from decimal import ROUND_HALF_UP, Decimal + +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st +from policyengine_core.parameters import ParameterNode + +from policyengine_uk.parameters.gov.economic_assumptions.create_september_cpi_uprating import ( + BASE_YEAR, + add_september_cpi_uprating, + september_cpi_uprating_rate, +) +from policyengine_uk.parameters.gov.economic_assumptions.create_economic_assumption_indices import ( + create_economic_assumption_indices, +) + +# September CPI since 1989 ranged from -0.1% (2015) to 10.1% (2022); draw +# wider, and off the published grid, to cover forecasts and scenarios. +any_rate = st.floats(min_value=-0.1, max_value=0.2, allow_nan=False) +published_rate = st.integers(min_value=-60, max_value=150).map(lambda n: n / 1000) +FIRST_SEPTEMBER = BASE_YEAR +# The index builder runs to 2039. +LAST_INDEX_YEAR = 2039 +PROPERTIES = settings( + max_examples=200, deadline=None, suppress_health_check=[HealthCheck.too_slow] +) + + +def septembers(min_size=2, max_size=25): + """September CPI for 2010 onward, one value per year.""" + return st.lists(any_rate, min_size=min_size, max_size=max_size).map( + lambda rates: {FIRST_SEPTEMBER + i: rate for i, rate in enumerate(rates)} + ) + + +def build(cpi_by_september): + """Run the model's builders on a tree holding only the inputs they read. + + The calendar-year series only bound the years covered: the inputs run + to the last September given. + """ + years = sorted(cpi_by_september) + tree = ParameterNode( + data={ + "gov": { + "economic_assumptions": { + "statutory_uprating_inputs": { + "cpi_september": { + "values": { + f"{year}-09-01": rate + for year, rate in cpi_by_september.items() + } + } + }, + "yoy_growth": { + "obr": { + series: { + "values": {f"{year}-01-01": 0.02 for year in years} + } + for series in ("consumer_price_index", "average_earnings") + } + }, + } + } + } + ) + tree = create_economic_assumption_indices(add_september_cpi_uprating(tree)) + economic_assumptions = tree.gov.economic_assumptions + rates = economic_assumptions.yoy_growth.september_cpi_uprating + index = economic_assumptions.indices.september_cpi_uprating + last_rate_year = max(years) + 1 + return ( + {year: rates(f"{year}-01-01") for year in range(BASE_YEAR, last_rate_year + 1)}, + { + year: index(f"{year}-01-01") + for year in range(BASE_YEAR, LAST_INDEX_YEAR + 1) + }, + ) + + +def published(rate): + return Decimal(repr(float(rate))).quantize(Decimal("0.001"), ROUND_HALF_UP) + + +@PROPERTIES +@given(cpi=any_rate) +def test_rate_is_the_published_september_figure_and_never_a_cut(cpi): + rate = september_cpi_uprating_rate(cpi) + assert rate >= 0 + assert str(rate) != "-0.0" + if published(cpi) > 0: + assert rate == float(published(cpi)) + assert abs(rate - cpi) <= 0.0005 + 1e-12 + else: + assert rate == 0 + + +@PROPERTIES +@given(cpi=any_rate, rise=st.floats(min_value=0, max_value=0.1)) +def test_rate_never_falls_when_september_cpi_rises(cpi, rise): + assert september_cpi_uprating_rate(cpi + rise) >= september_cpi_uprating_rate(cpi) + + +@PROPERTIES +@given(cpi=published_rate) +def test_published_figures_pass_through(cpi): + assert september_cpi_uprating_rate(cpi) == max(cpi, 0) + assert september_cpi_uprating_rate( + september_cpi_uprating_rate(cpi) + ) == september_cpi_uprating_rate(cpi) + + +@PROPERTIES +@given(cpi=septembers()) +def test_each_april_rises_by_the_previous_september(cpi): + rates, _ = build(cpi) + assert rates[BASE_YEAR] is None + for year in range(BASE_YEAR + 1, max(cpi) + 2): + assert rates[year] == september_cpi_uprating_rate(cpi[year - 1]), year + + +@PROPERTIES +@given(cpi=septembers()) +def test_index_compounds_the_rises(cpi): + """Against a Decimal compounding of the inputs: the builder rounds the + index (never below 1) to 5 decimal places each year, so allow a relative + 5e-6 per year.""" + rates, index = build(cpi) + last_rate_year = max(cpi) + 1 + reference = Decimal(1) + assert index[BASE_YEAR] == 1 + for year in range(BASE_YEAR + 1, LAST_INDEX_YEAR + 1): + # Past the inputs the last rise carries forward, as for every series. + rise = max(published(cpi[min(year, last_rate_year) - 1]), Decimal(0)) + reference *= 1 + rise + tolerance = 5e-6 * (year - BASE_YEAR) + assert abs(index[year] / float(reference) - 1) <= tolerance, year + assert index[year] == round( + index[year - 1] * (1 + rates[min(year, last_rate_year)]), 5 + ) + + +@PROPERTIES +@given(cpi=septembers()) +def test_index_never_falls(cpi): + _, index = build(cpi) + for year in range(BASE_YEAR + 1, LAST_INDEX_YEAR + 1): + assert index[year] >= index[year - 1], year + + +@PROPERTIES +@given( + cpi=septembers(min_size=3), + position=st.integers(min_value=0, max_value=24), + rise=st.floats(min_value=0.0005, max_value=0.1), +) +def test_a_higher_september_raises_the_index_only_from_the_next_april( + cpi, position, rise +): + changed_september = FIRST_SEPTEMBER + position % len(cpi) + higher = {**cpi, changed_september: cpi[changed_september] + rise} + _, index = build(cpi) + _, higher_index = build(higher) + for year in range(BASE_YEAR, LAST_INDEX_YEAR + 1): + assert higher_index[year] >= index[year], year + if year <= changed_september: + assert higher_index[year] == index[year], year + + +@PROPERTIES +@given(cpi=septembers()) +def test_building_twice_gives_the_same_series(cpi): + assert build(cpi) == build(dict(cpi)) diff --git a/policyengine_uk/tests/test_triple_lock_statutory_inputs.py b/policyengine_uk/tests/test_triple_lock_statutory_inputs.py index a3101905d0..5ada9b93df 100644 --- a/policyengine_uk/tests/test_triple_lock_statutory_inputs.py +++ b/policyengine_uk/tests/test_triple_lock_statutory_inputs.py @@ -52,10 +52,9 @@ def statutory_inputs(parameters): def test_april_2027_uses_published_may_july_2026_earnings(): - """May-July 2026 total pay growth was 3.9% (first estimate), above the - model's September 2026 CPI forecast (the OBR's 2.1%; August 2026 CPI was - 3.1%). Unless September CPI exceeds 3.9%, the new State Pension rises from - £241.30 to £250.71 (before rounding to 5p).""" + """May-July 2026 total pay growth was 3.9% (first estimate), above + September 2026 CPI (3.5%, ONS 21 October 2026), so the new State Pension + rises from £241.30 to £250.71 (before rounding to 5p).""" parameters = system.parameters assert uprating(parameters, 2027) == pytest.approx(0.039) assert new_state_pension_weekly(parameters, 2027) == pytest.approx( @@ -67,7 +66,7 @@ def test_every_published_year_has_its_own_value(): """A missing year would silently carry the previous year's figure.""" inputs = statutory_inputs(system.parameters) for name, month_day, last_published in [ - ("cpi_september", "09-01", 2025), + ("cpi_september", "09-01", 2026), ("awe_total_pay_may_july", "07-01", 2026), ]: parameter = getattr(inputs, name) @@ -79,7 +78,7 @@ def test_every_published_year_has_its_own_value(): @pytest.mark.parametrize( "name, series, month_day, first_forecast_year", [ - ("cpi_september", "consumer_price_index", "09-01", 2026), + ("cpi_september", "consumer_price_index", "09-01", 2027), ("awe_total_pay_may_july", "average_earnings", "07-01", 2027), ], ) @@ -107,7 +106,6 @@ def test_forecasts_are_calendar_growth_plus_the_obr_gap( # uprate thresholds"), Q3 CPI from economy Table 1.7 for 2030, and Q2 average # earnings growth on a year earlier from economy Table 1.6. OBR_SEPTEMBER_CPI = { - 2026: 0.021157, 2027: 0.020719, 2028: 0.020299, 2029: 0.020480, @@ -439,7 +437,7 @@ def test_horizon_ends_with_the_shorter_calendar_series(): def test_the_rule_needs_the_forecasts_filled_in_first(): - """Read before the forecasts are filled in, September 2026 CPI is still + """Read before the forecasts are filled in, September 2027 CPI is still null, and the rule refuses rather than guessing.""" from policyengine_uk.parameters.gov.dwp.state_pension.triple_lock.create_triple_lock import ( read_uprating_years, @@ -448,5 +446,5 @@ def test_the_rule_needs_the_forecasts_filled_in_first(): raw = CountryTaxBenefitSystem() raw.reset_parameters() - with pytest.raises(ValueError, match="September 2026 CPI"): + with pytest.raises(ValueError, match="September 2027 CPI"): read_uprating_years(raw.parameters) From 0618038f2320621dcd4b3e6bc4e911eb9f6f2a2a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 22 Oct 2026 07:26:59 -0400 Subject: [PATCH 2/5] Fix childcare funding projection test to use lagged CPI --- policyengine_uk/tests/test_childcare_funding_rates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/policyengine_uk/tests/test_childcare_funding_rates.py b/policyengine_uk/tests/test_childcare_funding_rates.py index 35daf38b16..212064b47e 100644 --- a/policyengine_uk/tests/test_childcare_funding_rates.py +++ b/policyengine_uk/tests/test_childcare_funding_rates.py @@ -158,7 +158,8 @@ def test_outside_england_retains_zero_entitlements(): def test_existing_cpi_projection_starts_from_the_2026_funding_anchor(): # This verifies the retained projection rule, not an official 2027 rate. p = system.parameters - factor = p("2027").gov.benefit_uprating_cpi / p("2026").gov.benefit_uprating_cpi + index = p.gov.economic_assumptions.indices.obr.lagged_cpi + factor = index("2027") / index("2026") expected = np.array([12.04, 8.90, 6.61]) * factor np.testing.assert_allclose( p("2027").gov.dfe.childcare_funding_rate.calc(AGES), expected, rtol=0, atol=1e-8 From 7f9883184b6063125d63908f236e30f0a9465ab0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 22 Oct 2026 07:27:57 -0400 Subject: [PATCH 3/5] Fix benefit uprating propagation and preserve HB income-band projections --- .../benefit-uprating-september-cpi.changed.md | 2 +- .../non_dep_deduction/amount.yaml | 103 +++++++++++------- .../elements/childcare/cap.yaml | 1 + .../gov/economic_assumptions/README.md | 18 ++- .../tests/test_september_cpi_uprating.py | 57 +++++++++- 5 files changed, 131 insertions(+), 50 deletions(-) diff --git a/changelog.d/benefit-uprating-september-cpi.changed.md b/changelog.d/benefit-uprating-september-cpi.changed.md index b911c8e3ae..f87664d152 100644 --- a/changelog.d/benefit-uprating-september-cpi.changed.md +++ b/changelog.d/benefit-uprating-september-cpi.changed.md @@ -1 +1 @@ -Uprate benefits each April by the previous September's CPI, the measure every Social Security Administration Act 1992 s150 review uses, instead of the previous calendar year's CPI (#2533). `gov.benefit_uprating_cpi` now follows `gov.economic_assumptions.indices.september_cpi_uprating`, so the Universal Credit standard allowance and health element indices built on it follow September CPI too, as the Universal Credit Act 2025 requires. Add September 2026 CPI (3.5%, ONS 21 October 2026), which sets the April 2027 rise. IIDB moves from same-year calendar CPI to the benefit index. Amounts outside the s150 prices review that were on the index (Pension Credit standard minimum guarantee and savings credit threshold, Housing Benefit pension-age allowances, DfE funding rates, council tax high value surcharge) keep their previous projection. +Uprate benefits each April by the previous September's CPI, the measure every Social Security Administration Act 1992 s150 review uses, instead of the previous calendar year's CPI (#2533). `gov.benefit_uprating_cpi` now follows `gov.economic_assumptions.indices.september_cpi_uprating`, so the Universal Credit standard allowance and health element indices built on it follow September CPI too, as the Universal Credit Act 2025 requires. Add September 2026 CPI (3.5%, ONS 21 October 2026), which sets the April 2027 rise. IIDB moves from same-year calendar CPI to the benefit index. Propagate the index to the Universal Credit childcare cap so it uprates after its last published value. Amounts not raised by September CPI in practice that were on the index (Pension Credit standard minimum guarantee and savings credit threshold, Housing Benefit pension-age allowances and non-dependant deduction income bands, DfE funding rates, council tax high value surcharge) keep their previous lagged calendar-year CPI projection. This remains a placeholder for the earnings-linked Pension Credit and Housing Benefit group; the savings credit threshold and pension-age allowances are within the s150 review. diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/amount.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/amount.yaml index af1b10a428..c9f34d38de 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/amount.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/amount.yaml @@ -4,9 +4,15 @@ description: The following amount is provided for individuals under the Housing # as set in the Housing Benefit Regulations 2006 reg 74 by each year's # Social Security Benefits Up-rating Order (bands unchanged in 2021-22: SI # 2021/162 art 24(3)). +# Deduction amounts rise by September CPI. Income bands follow earnings in +# practice; retain their previous lagged calendar-year CPI projection as a +# placeholder until the earnings-linked uprating model covers them. brackets: - threshold: - 2018-06-18: 0 + metadata: + uprating: gov.economic_assumptions.indices.obr.lagged_cpi + values: + 2018-06-18: 0 amount: 2018-06-18: 15.25 2019-04-01: 15.6 @@ -18,14 +24,17 @@ brackets: 2025-04-01: 19.65 2026-04-01: 20.4 - threshold: - 2018-06-18: 139 - 2019-04-01: 143 - 2020-04-01: 149 - 2022-04-01: 154 - 2023-04-01: 162 - 2024-04-01: 176 - 2025-04-01: 183 - 2026-04-01: 192 + metadata: + uprating: gov.economic_assumptions.indices.obr.lagged_cpi + values: + 2018-06-18: 139 + 2019-04-01: 143 + 2020-04-01: 149 + 2022-04-01: 154 + 2023-04-01: 162 + 2024-04-01: 176 + 2025-04-01: 183 + 2026-04-01: 192 amount: 2018-06-18: 35 2019-04-01: 35.85 @@ -37,14 +46,17 @@ brackets: 2025-04-01: 45.15 2026-04-01: 46.85 - threshold: - 2018-06-18: 204 - 2019-04-01: 209 - 2020-04-01: 217 - 2022-04-01: 224 - 2023-04-01: 236 - 2024-04-01: 256 - 2025-04-01: 266 - 2026-04-01: 279 + metadata: + uprating: gov.economic_assumptions.indices.obr.lagged_cpi + values: + 2018-06-18: 204 + 2019-04-01: 209 + 2020-04-01: 217 + 2022-04-01: 224 + 2023-04-01: 236 + 2024-04-01: 256 + 2025-04-01: 266 + 2026-04-01: 279 amount: 2018-06-18: 48.05 2019-04-01: 49.2 @@ -56,14 +68,17 @@ brackets: 2025-04-01: 62 2026-04-01: 64.35 - threshold: - 2018-06-18: 265 - 2019-04-01: 272 - 2020-04-01: 283 - 2022-04-01: 292 - 2023-04-01: 308 - 2024-04-01: 334 - 2025-04-01: 348 - 2026-04-01: 365 + metadata: + uprating: gov.economic_assumptions.indices.obr.lagged_cpi + values: + 2018-06-18: 265 + 2019-04-01: 272 + 2020-04-01: 283 + 2022-04-01: 292 + 2023-04-01: 308 + 2024-04-01: 334 + 2025-04-01: 348 + 2026-04-01: 365 amount: 2018-06-18: 78.65 2019-04-01: 80.55 @@ -75,14 +90,17 @@ brackets: 2025-04-01: 101.35 2026-04-01: 105.2 - threshold: - 2018-06-18: 354 - 2019-04-01: 363 - 2020-04-01: 377 - 2022-04-01: 389 - 2023-04-01: 410 - 2024-04-01: 445 - 2025-04-01: 463 - 2026-04-01: 485 + metadata: + uprating: gov.economic_assumptions.indices.obr.lagged_cpi + values: + 2018-06-18: 354 + 2019-04-01: 363 + 2020-04-01: 377 + 2022-04-01: 389 + 2023-04-01: 410 + 2024-04-01: 445 + 2025-04-01: 463 + 2026-04-01: 485 amount: 2018-06-18: 89.55 2019-04-01: 91.7 @@ -94,14 +112,17 @@ brackets: 2025-04-01: 115.45 2026-04-01: 119.85 - threshold: - 2018-06-18: 439 - 2019-04-01: 451 - 2020-04-01: 469 - 2022-04-01: 484 - 2023-04-01: 511 - 2024-04-01: 554 - 2025-04-01: 577 - 2026-04-01: 605 + metadata: + uprating: gov.economic_assumptions.indices.obr.lagged_cpi + values: + 2018-06-18: 439 + 2019-04-01: 451 + 2020-04-01: 469 + 2022-04-01: 484 + 2023-04-01: 511 + 2024-04-01: 554 + 2025-04-01: 577 + 2026-04-01: 605 amount: 2018-06-18: 98.3 2019-04-01: 100.65 diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/elements/childcare/cap.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/elements/childcare/cap.yaml index fe924d8984..0de18e5fbc 100644 --- a/policyengine_uk/parameters/gov/dwp/universal_credit/elements/childcare/cap.yaml +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/elements/childcare/cap.yaml @@ -15,6 +15,7 @@ metadata: period: month unit: currency-GBP uprating: gov.benefit_uprating_cpi + propagate_metadata_to_children: true reference: - title: The Universal Credit Regulations 2013 reg. 31 (childcare costs element), amounts in reg. 36 href: https://www.legislation.gov.uk/uksi/2013/376/regulation/36 diff --git a/policyengine_uk/parameters/gov/economic_assumptions/README.md b/policyengine_uk/parameters/gov/economic_assumptions/README.md index e760936032..5222b340c7 100644 --- a/policyengine_uk/parameters/gov/economic_assumptions/README.md +++ b/policyengine_uk/parameters/gov/economic_assumptions/README.md @@ -195,11 +195,19 @@ Order Explanatory Memorandum since April 2011; 3.8% for April 2026, written statement HCWS1101). `gov.benefit_uprating_cpi`, which the benefit rate parameters name as their `uprating`, follows `indices.september_cpi_uprating`, and so do the Universal Credit standard allowance and health element indices -built from it. Amounts outside the s150 prices review that sat on -`gov.benefit_uprating_cpi` (the Pension Credit standard minimum guarantee and -savings credit threshold, Housing Benefit pension-age allowances, DfE funding -rates and the council tax high value surcharge) keep the lagged calendar-year -CPI projection they had, through `indices.obr.lagged_cpi`. +built from it. Amounts not raised by September CPI in practice that sat on +`gov.benefit_uprating_cpi` keep the lagged calendar-year CPI projection they +had, through `indices.obr.lagged_cpi`. These include the earnings-linked +Pension Credit standard minimum guarantee, savings credit threshold, Housing +Benefit pension-age allowances and Housing Benefit non-dependant deduction +income bands, as well as DfE funding rates and the council tax high value +surcharge. Lagged calendar-year CPI remains a placeholder pending an +appropriate earnings-linked model for the first group; deduction amounts +continue to follow September CPI. The savings credit threshold and Housing +Benefit pension-age allowances are within the s150 review, under +[s150(1)(l) and (h)](https://www.legislation.gov.uk/ukpga/1992/5/section/150), +even though their increases in practice are linked to earnings rather than +September CPI. Income Tax Act 2007 ss21 and 57 index the basic rate limit and the personal allowance by this measure once no freeze applies. diff --git a/policyengine_uk/tests/test_september_cpi_uprating.py b/policyengine_uk/tests/test_september_cpi_uprating.py index d72c462a40..f6d4413164 100644 --- a/policyengine_uk/tests/test_september_cpi_uprating.py +++ b/policyengine_uk/tests/test_september_cpi_uprating.py @@ -9,7 +9,7 @@ """ import pytest -from policyengine_core.parameters import Parameter, load_parameter_file +from policyengine_core.parameters import Parameter, ParameterNode, load_parameter_file from policyengine_uk import Simulation from policyengine_uk.model_api import Scenario @@ -134,9 +134,44 @@ def last_published_year(raw, name): return max(int(value.instant_str[:4]) for value in raw.get_child(name).values_list) +# An older, unrelated omission: the national financial wealth total is meant +# to project by GDP per capita, but its node metadata never reaches this leaf. +# Keep its existing corporate-incidence fallback unchanged in this benefit fix. +# Limit the exception to this exact leaf so new omissions still fail. +KNOWN_UNPROPAGATED_UPRATING = { + ( + "household.wealth.national_balance_sheet.household", + "household.wealth.national_balance_sheet.household.financial_net_worth", + ), +} + + +def test_node_uprating_reaches_every_leaf(): + """A node's uprating must reach its leaves, which core actually uprates. + + A leaf can override the parent's index, as the earnings-linked HB income + bands do, but must not silently lose uprating altogether. + """ + missing = set() + for node in system.parameters.get_descendants(): + if not isinstance(node, ParameterNode) or not uprating_parameter(node): + continue + for leaf in node.get_descendants(): + if isinstance(leaf, Parameter) and not uprating_parameter(leaf): + missing.add((node.name, leaf.name)) + assert missing <= KNOWN_UNPROPAGATED_UPRATING, missing - KNOWN_UNPROPAGATED_UPRATING + + +@pytest.mark.parametrize("children, expected", [("1", 1108.58), ("2", 1900.42)]) +def test_uc_childcare_caps_rise_by_september_cpi_in_april_2027(children, expected): + cap = system.parameters.gov.dwp.universal_credit.elements.childcare.cap + assert cap.children[children]("2027-04-30") == pytest.approx(expected, abs=0.005) + + def test_every_parameter_on_the_index_rises_by_september_cpi_after_its_last_value(): """After its last published value, each parameter uprated by the index - rises by exactly the April rise: 0% in April 2016, 2.1% in April 2027.""" + rises by exactly the April rise: 0% in April 2016, 3.5% in April 2027, + and 2.1% in April 2028.""" parameters = system.parameters # The processed tree, including its baseline copy, is uprated, so read # the YAML files for the last published dates. @@ -186,7 +221,23 @@ def test_no_parameter_projects_from_an_earlier_start(): LAGGED_CPI = "gov.economic_assumptions.indices.obr.lagged_cpi" -def test_amounts_outside_the_review_keep_their_lagged_calendar_cpi_projection(): +def test_hb_non_dependant_income_bands_keep_lagged_cpi_while_deductions_use_september(): + parameters = system.parameters + amounts = parameters.gov.dwp.housing_benefit.non_dep_deduction.amount + index = parameters.gov.economic_assumptions.indices.obr.lagged_cpi + lagged_factor = index("2027-04-30") / index("2026-04-30") + for bracket in amounts.brackets: + assert uprating_parameter(bracket.threshold) == LAGGED_CPI + assert uprating_parameter(bracket.amount) == INDEX + assert bracket.threshold("2027-04-30") == pytest.approx( + bracket.threshold("2026-04-30") * lagged_factor, abs=PENNY_TENTH + ) + assert bracket.amount("2027-04-30") == pytest.approx( + bracket.amount("2026-04-30") * 1.035, abs=PENNY_TENTH + ) + + +def test_amounts_not_raised_by_september_cpi_keep_their_lagged_calendar_projection(): """Amounts that were on gov.benefit_uprating_cpi but are not raised by September CPI (the Pension Credit standard minimum guarantee, the Housing Benefit pension-age allowances, DfE funding rates and others) still rise From 567c1df112556e8b45bfafab61ffe90a6deaac23 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 22 Oct 2026 07:28:30 -0400 Subject: [PATCH 4/5] Refresh ONS earnings citation and pin April 2027 triple lock --- .../awe_total_pay_may_july.yaml | 7 ++++--- .../tests/test_triple_lock_statutory_inputs.py | 13 ++++++++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/policyengine_uk/parameters/gov/economic_assumptions/statutory_uprating_inputs/awe_total_pay_may_july.yaml b/policyengine_uk/parameters/gov/economic_assumptions/statutory_uprating_inputs/awe_total_pay_may_july.yaml index 8da101a37e..1c035bb0bd 100644 --- a/policyengine_uk/parameters/gov/economic_assumptions/statutory_uprating_inputs/awe_total_pay_may_july.yaml +++ b/policyengine_uk/parameters/gov/economic_assumptions/statutory_uprating_inputs/awe_total_pay_may_july.yaml @@ -26,9 +26,8 @@ values: 2023-07-01: 0.085 # SI 2024/242 EM 2024-07-01: 0.041 # SI 2025/295 EM 2025-07-01: 0.048 # SI 2026/148 EM - # Provisional: 3.9% is the 15 September 2026 first estimate and stays - # provisional until the October labour market release (due 20 October), - # the vintage the uprating review uses; replace it once published. + # ONS KAC3, 20 October 2026 labour market release: May-July growth + # remained 3.9%, unchanged from the September first estimate. 2026-07-01: 0.039 # Not yet published. From here the value is calendar-year earnings growth # (yoy_growth.obr.average_earnings) plus forecast_gap.awe_total_pay_may_july. @@ -40,6 +39,8 @@ metadata: reference: - title: ONS KAC3, AWE whole economy total pay, three-month average growth on a year earlier href: https://www.ons.gov.uk/employmentandlabourmarket/peopleinwork/earningsandworkinghours/timeseries/kac3/lms + - title: ONS Average weekly earnings in Great Britain, 20 October 2026 + href: https://www.ons.gov.uk/employmentandlabourmarket/peopleinwork/employmentandemployeetypes/bulletins/averageweeklyearningsingreatbritain/october2026 - title: Explanatory Memorandum to the Social Security Benefits Up-rating Order 2026 (SI 2026/148) href: https://www.legislation.gov.uk/uksi/2026/148/contents/made - title: Explanatory Memorandum to the Social Security Benefits Up-rating Order 2025 (SI 2025/295) diff --git a/policyengine_uk/tests/test_triple_lock_statutory_inputs.py b/policyengine_uk/tests/test_triple_lock_statutory_inputs.py index 5ada9b93df..69a84d1a1f 100644 --- a/policyengine_uk/tests/test_triple_lock_statutory_inputs.py +++ b/policyengine_uk/tests/test_triple_lock_statutory_inputs.py @@ -52,10 +52,17 @@ def statutory_inputs(parameters): def test_april_2027_uses_published_may_july_2026_earnings(): - """May-July 2026 total pay growth was 3.9% (first estimate), above - September 2026 CPI (3.5%, ONS 21 October 2026), so the new State Pension - rises from £241.30 to £250.71 (before rounding to 5p).""" + """ONS confirmed May-July 2026 total pay growth at 3.9% on 20 October, + unchanged from September. April 2027's triple lock is max(3.5% CPI, + 3.9% earnings, 2.5%), so the new State Pension rises from £241.30 to + £250.71 (before rounding to 5p).""" parameters = system.parameters + inputs = statutory_inputs(parameters) + assert inputs.cpi_september("2026-09-01") == pytest.approx(0.035) + assert inputs.awe_total_pay_may_july("2026-07-01") == pytest.approx(0.039) + assert parameters.gov.dwp.state_pension.triple_lock.minimum_rate( + "2027-04-30" + ) == pytest.approx(0.025) assert uprating(parameters, 2027) == pytest.approx(0.039) assert new_state_pension_weekly(parameters, 2027) == pytest.approx( 241.30 * 1.039, abs=0.005 From 9f2ebfc829684052b526b06874f3c6f725ec608d Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 22 Oct 2026 10:56:31 -0400 Subject: [PATCH 5/5] Keep HB non-dependant income bands on lagged CPI The node's propagate_metadata_to_children copies its metadata onto every descendant with dict.update (policyengine-core propagate_parameter_metadata.py:20-28), so the node-level uprating: gov.benefit_uprating_cpi overwrote each band threshold's own lagged-CPI uprating and test_hb_non_dependant_income_bands_keep_lagged_cpi_ while_deductions_use_september failed in CI. Move the September CPI uprating from the node onto each deduction amount; units and references still propagate. Co-Authored-By: Claude Opus 5.5 --- .../non_dep_deduction/amount.yaml | 127 ++++++++++-------- 1 file changed, 72 insertions(+), 55 deletions(-) diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/amount.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/amount.yaml index c9f34d38de..55460318b2 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/amount.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/amount.yaml @@ -14,15 +14,18 @@ brackets: values: 2018-06-18: 0 amount: - 2018-06-18: 15.25 - 2019-04-01: 15.6 - 2020-04-01: 15.85 - 2021-04-01: 15.95 - 2022-04-01: 16.45 - 2023-04-01: 18.1 - 2024-04-01: 19.3 - 2025-04-01: 19.65 - 2026-04-01: 20.4 + metadata: + uprating: gov.benefit_uprating_cpi + values: + 2018-06-18: 15.25 + 2019-04-01: 15.6 + 2020-04-01: 15.85 + 2021-04-01: 15.95 + 2022-04-01: 16.45 + 2023-04-01: 18.1 + 2024-04-01: 19.3 + 2025-04-01: 19.65 + 2026-04-01: 20.4 - threshold: metadata: uprating: gov.economic_assumptions.indices.obr.lagged_cpi @@ -36,15 +39,18 @@ brackets: 2025-04-01: 183 2026-04-01: 192 amount: - 2018-06-18: 35 - 2019-04-01: 35.85 - 2020-04-01: 36.45 - 2021-04-01: 36.65 - 2022-04-01: 37.8 - 2023-04-01: 41.6 - 2024-04-01: 44.4 - 2025-04-01: 45.15 - 2026-04-01: 46.85 + metadata: + uprating: gov.benefit_uprating_cpi + values: + 2018-06-18: 35 + 2019-04-01: 35.85 + 2020-04-01: 36.45 + 2021-04-01: 36.65 + 2022-04-01: 37.8 + 2023-04-01: 41.6 + 2024-04-01: 44.4 + 2025-04-01: 45.15 + 2026-04-01: 46.85 - threshold: metadata: uprating: gov.economic_assumptions.indices.obr.lagged_cpi @@ -58,15 +64,18 @@ brackets: 2025-04-01: 266 2026-04-01: 279 amount: - 2018-06-18: 48.05 - 2019-04-01: 49.2 - 2020-04-01: 50.05 - 2021-04-01: 50.3 - 2022-04-01: 51.85 - 2023-04-01: 57.1 - 2024-04-01: 60.95 - 2025-04-01: 62 - 2026-04-01: 64.35 + metadata: + uprating: gov.benefit_uprating_cpi + values: + 2018-06-18: 48.05 + 2019-04-01: 49.2 + 2020-04-01: 50.05 + 2021-04-01: 50.3 + 2022-04-01: 51.85 + 2023-04-01: 57.1 + 2024-04-01: 60.95 + 2025-04-01: 62 + 2026-04-01: 64.35 - threshold: metadata: uprating: gov.economic_assumptions.indices.obr.lagged_cpi @@ -80,15 +89,18 @@ brackets: 2025-04-01: 348 2026-04-01: 365 amount: - 2018-06-18: 78.65 - 2019-04-01: 80.55 - 2020-04-01: 81.9 - 2021-04-01: 82.3 - 2022-04-01: 84.85 - 2023-04-01: 93.4 - 2024-04-01: 99.65 - 2025-04-01: 101.35 - 2026-04-01: 105.2 + metadata: + uprating: gov.benefit_uprating_cpi + values: + 2018-06-18: 78.65 + 2019-04-01: 80.55 + 2020-04-01: 81.9 + 2021-04-01: 82.3 + 2022-04-01: 84.85 + 2023-04-01: 93.4 + 2024-04-01: 99.65 + 2025-04-01: 101.35 + 2026-04-01: 105.2 - threshold: metadata: uprating: gov.economic_assumptions.indices.obr.lagged_cpi @@ -102,15 +114,18 @@ brackets: 2025-04-01: 463 2026-04-01: 485 amount: - 2018-06-18: 89.55 - 2019-04-01: 91.7 - 2020-04-01: 93.25 - 2021-04-01: 93.7 - 2022-04-01: 96.6 - 2023-04-01: 106.35 - 2024-04-01: 113.5 - 2025-04-01: 115.45 - 2026-04-01: 119.85 + metadata: + uprating: gov.benefit_uprating_cpi + values: + 2018-06-18: 89.55 + 2019-04-01: 91.7 + 2020-04-01: 93.25 + 2021-04-01: 93.7 + 2022-04-01: 96.6 + 2023-04-01: 106.35 + 2024-04-01: 113.5 + 2025-04-01: 115.45 + 2026-04-01: 119.85 - threshold: metadata: uprating: gov.economic_assumptions.indices.obr.lagged_cpi @@ -124,15 +139,18 @@ brackets: 2025-04-01: 577 2026-04-01: 605 amount: - 2018-06-18: 98.3 - 2019-04-01: 100.65 - 2020-04-01: 102.35 - 2021-04-01: 102.85 - 2022-04-01: 106.05 - 2023-04-01: 116.75 - 2024-04-01: 124.55 - 2025-04-01: 126.65 - 2026-04-01: 131.45 + metadata: + uprating: gov.benefit_uprating_cpi + values: + 2018-06-18: 98.3 + 2019-04-01: 100.65 + 2020-04-01: 102.35 + 2021-04-01: 102.85 + 2022-04-01: 106.05 + 2023-04-01: 116.75 + 2024-04-01: 124.55 + 2025-04-01: 126.65 + 2026-04-01: 131.45 metadata: amount_unit: currency-GBP @@ -141,7 +159,6 @@ metadata: propagate_metadata_to_children: true threshold_unit: currency-GBP type: single_amount - uprating: gov.benefit_uprating_cpi reference: - title: The Housing Benefit Regulations 2006 sec. 74 href: https://www.legislation.gov.uk/uksi/2006/213/regulation/74/2024-04-01