diff --git a/changelog.d/2072.fixed.md b/changelog.d/2072.fixed.md new file mode 100644 index 0000000000..a93ededf80 --- /dev/null +++ b/changelog.d/2072.fixed.md @@ -0,0 +1 @@ +- Corrects the Pension Credit standard minimum guarantee projection to follow the statutory May-July earnings input with a non-negative floor. 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 0c0d5c29b9..53d53a590a 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 @@ -1,4 +1,8 @@ -description: Minimum income guarantee (basic amount) for Pension Credit. +description: >- + Minimum income guarantee (basic amount) for Pension Credit. Values after the + last enacted rate use the non-negative May-July average weekly earnings input + from the annual statutory review as a forecast floor; they are not enacted + future rates. SINGLE: values: 2015-04-06: 151.2 @@ -14,7 +18,7 @@ SINGLE: 2025-04-01: 221.85 2026-04-01: 238 metadata: - uprating: gov.benefit_uprating_cpi + uprating: gov.economic_assumptions.indices.statutory_earnings_floor COUPLE: values: 2015-04-06: 230.85 @@ -30,7 +34,7 @@ COUPLE: 2025-04-01: 338.61 2026-04-01: 363.25 metadata: - uprating: gov.benefit_uprating_cpi + uprating: gov.economic_assumptions.indices.statutory_earnings_floor metadata: period: week label: Pension Credit minimum guarantee @@ -40,3 +44,9 @@ metadata: href: https://www.legislation.gov.uk/uksi/2002/1792/regulation/6/2021-04-12 - title: Benefit and pension rates 2026 to 2027 href: https://www.gov.uk/government/publications/benefit-and-pension-rates-2026-to-2027/proposed-benefit-and-pension-rates-2026-to-2027 + - title: Social Security Administration Act 1992 section 150A + href: https://www.legislation.gov.uk/ukpga/1992/5/section/150A + - title: Social Security Administration (Northern Ireland) Act 1992 section 132A + href: https://www.legislation.gov.uk/ukpga/1992/8/section/132A + - title: A14/2025 Housing Benefit uprating for the financial year ending March 2027 + 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/state_pension/triple_lock/create_triple_lock.py b/policyengine_uk/parameters/gov/dwp/state_pension/triple_lock/create_triple_lock.py index b977aca217..007774cb82 100644 --- a/policyengine_uk/parameters/gov/dwp/state_pension/triple_lock/create_triple_lock.py +++ b/policyengine_uk/parameters/gov/dwp/state_pension/triple_lock/create_triple_lock.py @@ -1,4 +1,4 @@ -"""Build the State Pension uprating series from the statutory inputs. +"""Build statutory earnings and State Pension uprating series. Each April the basic and new State Pension rise by the highest of: @@ -29,10 +29,13 @@ earnings path started from its level in the year before the guarantee first applied. Current law leaves it off. -The output parameter ``gov.economic_assumptions.yoy_growth.triple_lock`` holds -the rate taking effect in April of each year, keyed to 1 January as the other -growth series are, and runs to one year past the last year of the -economic-assumption series. +The output parameters hold the rates taking effect in April of each year, +keyed to 1 January as the other growth series are. ``triple_lock`` applies the +State Pension rule. ``statutory_earnings_floor`` holds the non-negative +earnings component for amounts, including the Pension Credit standard minimum +guarantee, that section 150A protects without extending the triple lock to +them. Both run to one year past the last year of the economic-assumption +series. """ from dataclasses import dataclass @@ -155,6 +158,13 @@ def uprating_rates(years: Dict[int, UpratingYear]) -> Dict[int, float]: return rates +def statutory_earnings_floor_rates( + years: Dict[int, UpratingYear], +) -> Dict[int, float]: + """Return the non-negative earnings rate from each statutory review.""" + return {year: max(inputs.earnings, 0.0) for year, inputs in years.items()} + + def _flag(parameter: Parameter, instant: str) -> bool: value = parameter(instant) return bool(value) if value is not None else False @@ -200,22 +210,53 @@ def read_uprating_years(parameters: ParameterNode) -> Dict[int, UpratingYear]: def add_triple_lock(parameters: ParameterNode) -> ParameterNode: - """Add ``gov.economic_assumptions.yoy_growth.triple_lock``.""" - rates = uprating_rates(read_uprating_years(parameters)) - new_parameter = Parameter( + """Add the statutory earnings floor and State Pension triple lock.""" + years = read_uprating_years(parameters) + triple_lock_rates = uprating_rates(years) + earnings_floor_rates = statutory_earnings_floor_rates(years) + triple_lock = Parameter( "gov.economic_assumptions.yoy_growth.triple_lock", data={ "description": ( "State Pension uprating taking effect in April of each year." ), - "values": {f"{year}-01-01": rate for year, rate in rates.items()}, + "values": { + f"{year}-01-01": rate for year, rate in triple_lock_rates.items() + }, "metadata": { "unit": "/1", "label": "State Pension uprating rate", }, }, ) + parameters.gov.economic_assumptions.yoy_growth.add_child("triple_lock", triple_lock) + statutory_earnings_floor = Parameter( + "gov.economic_assumptions.yoy_growth.statutory_earnings_floor", + data={ + "description": ( + "Non-negative May-July average weekly earnings growth used " + "as the statutory uprating floor under section 150A." + ), + "values": { + f"{year}-01-01": rate for year, rate in earnings_floor_rates.items() + }, + "metadata": { + "unit": "/1", + "label": "Statutory earnings uprating floor", + "reference": [ + { + "title": ( + "Social Security Administration Act 1992 section 150A" + ), + "href": ( + "https://www.legislation.gov.uk/ukpga/1992/5/section/150A" + ), + } + ], + }, + }, + ) parameters.gov.economic_assumptions.yoy_growth.add_child( - "triple_lock", new_parameter + "statutory_earnings_floor", statutory_earnings_floor ) return parameters diff --git a/policyengine_uk/tests/test_triple_lock_statutory_inputs.py b/policyengine_uk/tests/test_triple_lock_statutory_inputs.py index a3101905d0..8320947bc1 100644 --- a/policyengine_uk/tests/test_triple_lock_statutory_inputs.py +++ b/policyengine_uk/tests/test_triple_lock_statutory_inputs.py @@ -4,9 +4,10 @@ earnings growth from the previous year. These tests check that forecasts of those inputs follow the OBR where it publishes them, fall back to calendar-year growth after, respond to macro scenarios applied before the -data load, and can be set directly; and that the earnings-path guarantee -models one reading of the plan announced in September 2026 as a -parameter reform. +data load, and can be set directly. They also check that Pension Credit uses +the same May-July earnings input without inheriting the CPI and 2.5% elements +of the State Pension triple lock, and that the earnings-path guarantee models +one reading of the plan announced in September 2026 as a parameter reform. """ import numpy as np @@ -43,10 +44,21 @@ def uprating(parameters, year): return parameters.gov.economic_assumptions.yoy_growth.triple_lock(f"{year}-01-01") +def statutory_earnings_floor(parameters, year): + return parameters.gov.economic_assumptions.yoy_growth.statutory_earnings_floor( + f"{year}-01-01" + ) + + def new_state_pension_weekly(parameters, year): return parameters.gov.dwp.state_pension.new_state_pension.amount(f"{year}-06-01") +def minimum_guarantee_weekly(parameters, relation_type, year): + guarantee = parameters.gov.dwp.pension_credit.guarantee_credit.minimum_guarantee + return getattr(guarantee, relation_type)(f"{year}-06-01") + + def statutory_inputs(parameters): return parameters.gov.economic_assumptions.statutory_uprating_inputs @@ -63,6 +75,42 @@ def test_april_2027_uses_published_may_july_2026_earnings(): ) +def test_pension_credit_uses_the_state_pension_statutory_earnings_input(): + parameters = system.parameters + + assert statutory_inputs(parameters).awe_total_pay_may_july( + "2026-07-01" + ) == pytest.approx(0.039) + assert statutory_earnings_floor(parameters, 2027) == pytest.approx(0.039) + assert minimum_guarantee_weekly(parameters, "SINGLE", 2027) == pytest.approx( + 238 * 1.039, rel=1e-5 + ) + assert minimum_guarantee_weekly(parameters, "COUPLE", 2027) == pytest.approx( + 363.25 * 1.039, rel=1e-5 + ) + + +def test_pension_credit_earnings_floor_rounds_and_never_reduces_the_guarantee(): + parameters = parameters_under( + { + f"{INPUTS}.awe_total_pay_may_july": { + "2027": 0.0124, + "2028": -0.004, + } + } + ) + + assert [ + statutory_earnings_floor(parameters, year) for year in (2028, 2029) + ] == pytest.approx([0.012, 0.0]) + for relation_type in ("SINGLE", "COUPLE"): + guarantee_2027 = minimum_guarantee_weekly(parameters, relation_type, 2027) + guarantee_2028 = minimum_guarantee_weekly(parameters, relation_type, 2028) + guarantee_2029 = minimum_guarantee_weekly(parameters, relation_type, 2029) + assert guarantee_2028 == pytest.approx(guarantee_2027 * 1.012, rel=1e-5) + assert guarantee_2029 == pytest.approx(guarantee_2028, rel=1e-5) + + 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)