From df43bf6b6ecce1f2ed2a173bda3812bdef1a5ab0 Mon Sep 17 00:00:00 2001 From: Anthony Volk <14987227+anth-volk@users.noreply.github.com> Date: Mon, 5 Oct 2026 22:40:13 +0400 Subject: [PATCH] Fix pension-age Housing Benefit allowance projection --- changelog.d/2154.fixed.md | 1 + .../create_protected_pension_age_uprating.py | 118 ++++++++++++++++++ .../allowances/lone_parent/aged.yaml | 9 +- .../allowances/single/aged.yaml | 9 +- policyengine_uk/tax_benefit_system.py | 4 + ...ng_benefit_protected_allowance_uprating.py | 54 ++++++++ 6 files changed, 191 insertions(+), 4 deletions(-) create mode 100644 changelog.d/2154.fixed.md create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/create_protected_pension_age_uprating.py create mode 100644 policyengine_uk/tests/test_housing_benefit_protected_allowance_uprating.py diff --git a/changelog.d/2154.fixed.md b/changelog.d/2154.fixed.md new file mode 100644 index 0000000000..b50c50d3fe --- /dev/null +++ b/changelog.d/2154.fixed.md @@ -0,0 +1 @@ +- Correct the forecast basis for protected pension-age Housing Benefit personal allowances. diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/create_protected_pension_age_uprating.py b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/create_protected_pension_age_uprating.py new file mode 100644 index 0000000000..9cc6bef50f --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/create_protected_pension_age_uprating.py @@ -0,0 +1,118 @@ +"""Project the protected pension-age Housing Benefit allowance. + +The protected single and lone-parent allowance consists of the Pension Credit +standard minimum guarantee plus the retained Savings Credit uplift. The two +components use different annual review inputs, so neither CPI nor earnings +alone is a faithful forecast proxy. +""" + +from policyengine_core.parameters import Parameter, ParameterNode + + +BASE_YEAR = 2026 +GUARANTEE_COMPONENT = 238.0 +SAVINGS_CREDIT_COMPONENT = 18.0 +BASE_ALLOWANCE = GUARANTEE_COMPONENT + SAVINGS_CREDIT_COMPONENT + + +def project_components( + guarantee: float, + savings_credit_uplift: float, + earnings_growth: float, + cpi_growth: float, +) -> tuple[float, float]: + """Project both components without reducing either cash amount.""" + return ( + guarantee * (1 + max(earnings_growth, 0)), + savings_credit_uplift * (1 + max(cpi_growth, 0)), + ) + + +def add_protected_pension_age_uprating( + parameters: ParameterNode, +) -> ParameterNode: + """Add the component-weighted forecast index used after April 2026.""" + inputs = parameters.gov.economic_assumptions.statutory_uprating_inputs + allowances = parameters.gov.dwp.housing_benefit.allowances + last_input_year = min( + max( + int(value.instant_str[:4]) + for value in input_parameter.values_list + if value.value is not None + ) + for input_parameter in ( + inputs.awe_total_pay_may_july, + inputs.cpi_september, + ) + ) + + guarantee = GUARANTEE_COMPONENT + savings_credit_uplift = SAVINGS_CREDIT_COMPONENT + values = {f"{BASE_YEAR}-04-01": 1.0} + for year in range(BASE_YEAR + 1, last_input_year + 2): + observation_year = year - 1 + guarantee, savings_credit_uplift = project_components( + guarantee, + savings_credit_uplift, + float(inputs.awe_total_pay_may_july(f"{observation_year}-07-01")), + float(inputs.cpi_september(f"{observation_year}-09-01")), + ) + values[f"{year}-04-01"] = (guarantee + savings_credit_uplift) / BASE_ALLOWANCE + + allowances.add_child( + "protected_pension_age_uprating", + Parameter( + "gov.dwp.housing_benefit.allowances.protected_pension_age_uprating", + data={ + "description": ( + "Forecast index for the protected pension-age Housing " + "Benefit allowance. The 2026-27 rounded base allocates " + "£238 to the Pension Credit guarantee and £18 to the " + "retained Savings Credit uplift. The components follow " + "non-negative May-to-July earnings and September CPI, " + "respectively. Projected values are not enacted rates." + ), + "values": values, + "metadata": { + "unit": "/1", + "label": ( + "Protected pension-age Housing Benefit allowance forecast index" + ), + "reference": [ + { + "title": ( + "A14/2025 Housing Benefit uprating for the " + "financial year ending March 2027, paragraphs " + "5 and 20" + ), + "href": ( + "https://www.gov.uk/government/publications/" + "housing-benefit-adjudication-circulars-2025/" + "a142025-housing-benefit-uprating-for-the-" + "financial-year-ending-march-2027" + ), + }, + { + "title": ( + "ONS KAC3 average weekly earnings, whole " + "economy, total pay" + ), + "href": ( + "https://www.ons.gov.uk/" + "employmentandlabourmarket/peopleinwork/" + "earningsandworkinghours/timeseries/kac3/lms" + ), + }, + { + "title": "ONS D7G7 Consumer Prices Index annual rate", + "href": ( + "https://www.ons.gov.uk/economy/" + "inflationandpriceindices/timeseries/d7g7/mm23" + ), + }, + ], + }, + }, + ), + ) + return parameters 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 0292f05a7c..b679298f75 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 @@ -1,4 +1,7 @@ -description: Personal allowance for Housing Benefit for a lone parent over State Pension age. +description: >- + Personal allowance for Housing Benefit for a lone parent over State Pension + age. Amounts after the last enacted rate use a component-weighted forecast + and are not enacted future rates. values: 2015-04-01: 181 @@ -13,9 +16,11 @@ metadata: period: week unit: currency-GBP label: Housing benefit lone parent aged personal allowance - uprating: gov.benefit_uprating_cpi + uprating: gov.dwp.housing_benefit.allowances.protected_pension_age_uprating reference: - title: The Housing Benefit Regulations 2006 Schedule 3 sec. 1 href: https://www.legislation.gov.uk/uksi/2006/213/data.pdf#page=212 - title: Benefit and pension rates 2024 to 2025 - Policy paper href: https://www.gov.uk/government/publications/benefit-and-pension-rates-2024-to-2025/benefit-and-pension-rates-2024-to-2025 + - title: A14/2025 Housing Benefit uprating for the financial year ending March 2027, paragraphs 5 and 20 + href: https://www.gov.uk/government/publications/housing-benefit-adjudication-circulars-2025/a142025-housing-benefit-uprating-for-the-financial-year-ending-march-2027 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 c3bf99f4ba..31a1593413 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 @@ -1,4 +1,7 @@ -description: The following personal allowance amount is provided to single filers over the pension age under the Housing Benefit. +description: >- + The following personal allowance amount is provided to single filers over + the pension age under the Housing Benefit. Amounts after the last enacted + rate use a component-weighted forecast and are not enacted future rates. values: 2015-04-01: 181 2021-04-01: 191.15 @@ -12,9 +15,11 @@ metadata: period: week unit: currency-GBP label: Housing benefit single aged personal allowance - uprating: gov.benefit_uprating_cpi + uprating: gov.dwp.housing_benefit.allowances.protected_pension_age_uprating reference: - title: The Housing Benefit Regulations 2006 Schedule 3 sec. 1 href: https://www.legislation.gov.uk/uksi/2006/213/data.pdf#page=212 - title: Benefit and pension rates 2024 to 2025 - Policy paper href: https://www.gov.uk/government/publications/benefit-and-pension-rates-2024-to-2025/benefit-and-pension-rates-2024-to-2025 + - title: A14/2025 Housing Benefit uprating for the financial year ending March 2027, paragraphs 5 and 20 + href: https://www.gov.uk/government/publications/housing-benefit-adjudication-circulars-2025/a142025-housing-benefit-uprating-for-the-financial-year-ending-march-2027 diff --git a/policyengine_uk/tax_benefit_system.py b/policyengine_uk/tax_benefit_system.py index c8264232cc..3c20e75e19 100644 --- a/policyengine_uk/tax_benefit_system.py +++ b/policyengine_uk/tax_benefit_system.py @@ -21,6 +21,9 @@ from policyengine_uk.parameters.gov.dwp.state_pension.triple_lock.create_triple_lock import ( add_triple_lock, ) +from policyengine_uk.parameters.gov.dwp.housing_benefit.allowances.create_protected_pension_age_uprating import ( + add_protected_pension_age_uprating, +) from policyengine_uk.parameters.gov.economic_assumptions.create_economic_assumption_indices import ( create_economic_assumption_indices, ) @@ -101,6 +104,7 @@ def process_parameters(self) -> None: self.parameters = add_lagged_earnings(self.parameters) self.parameters = add_lagged_cpi(self.parameters) self.parameters = add_statutory_uprating_inputs(self.parameters) + self.parameters = add_protected_pension_age_uprating(self.parameters) self.parameters = add_triple_lock(self.parameters) self.parameters = create_economic_assumption_indices(self.parameters) self.parameters = add_lsr_deprecation_aliases(self.parameters) diff --git a/policyengine_uk/tests/test_housing_benefit_protected_allowance_uprating.py b/policyengine_uk/tests/test_housing_benefit_protected_allowance_uprating.py new file mode 100644 index 0000000000..7242a4d98a --- /dev/null +++ b/policyengine_uk/tests/test_housing_benefit_protected_allowance_uprating.py @@ -0,0 +1,54 @@ +import pytest + +from policyengine_uk import system +from policyengine_uk.parameters.gov.dwp.housing_benefit.allowances.create_protected_pension_age_uprating import ( + BASE_ALLOWANCE, + GUARANTEE_COMPONENT, + SAVINGS_CREDIT_COMPONENT, + project_components, +) + + +def _parameters(): + return system.parameters + + +def test_protected_allowance_index_combines_the_two_statutory_inputs(): + parameters = _parameters() + inputs = parameters.gov.economic_assumptions.statutory_uprating_inputs + index = parameters.gov.dwp.housing_benefit.allowances.protected_pension_age_uprating + + guarantee, savings_credit_uplift = project_components( + GUARANTEE_COMPONENT, + SAVINGS_CREDIT_COMPONENT, + inputs.awe_total_pay_may_july("2026-07-01"), + inputs.cpi_september("2026-09-01"), + ) + + assert inputs.awe_total_pay_may_july("2026-07-01") == pytest.approx(0.039) + assert inputs.cpi_september("2026-09-01") == pytest.approx(0.02116) + assert index("2026-04-01") == 1 + expected_index = (guarantee + savings_credit_uplift) / BASE_ALLOWANCE + assert expected_index == pytest.approx(1.037745625) + assert index("2027-04-01") == pytest.approx(expected_index) + + +def test_single_and_lone_parent_use_the_composite_projection(): + allowances = _parameters().gov.dwp.housing_benefit.allowances + index = allowances.protected_pension_age_uprating + expected_2027 = 256 * index("2027-04-01") / index("2026-04-01") + + assert allowances.single.aged("2026-04-01") == 256 + assert allowances.lone_parent.aged("2026-04-01") == 256 + assert expected_2027 == pytest.approx(265.66288) + assert allowances.single.aged("2027-04-01") == pytest.approx(expected_2027) + assert allowances.lone_parent.aged("2027-04-01") == pytest.approx(expected_2027) + assert allowances.single.aged("2027-04-01") != pytest.approx( + 256 + * _parameters().gov.benefit_uprating_cpi("2027-04-01") + / _parameters().gov.benefit_uprating_cpi("2026-04-01") + ) + + +def test_component_projection_does_not_reduce_cash_amounts(): + assert project_components(238, 18, -0.01, -0.02) == (238, 18)