Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2247.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Pay pre-2026 Universal Credit health element claimants the legislated protected LCWRA amount (£429.80 a month in 2026-27, one amount for every claimant type), and project later years at the Universal Credit Act 2025 s. 4 floor.
9 changes: 3 additions & 6 deletions docs/book/policy/uc-rebalancing.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,9 @@ The reforms combine a higher standard allowance, protected awards for existing h

From April 2026, new Universal Credit claimants who qualify for the Limited Capability for Work-Related Activity (LCWRA) element receive a fixed monthly amount of £217.26.

Existing recipients are treated differently. Their LCWRA amount is uprated so that the combined value of:
Existing recipients are treated differently. Universal Credit Regulations 2013 reg. 36 gives one amount, the protected LCWRA amount, to every pre-2026 claimant, severe conditions criteria claimant or terminally ill claimant, whatever their age or couple status. For 2026-27 it is £429.80 a month ([SI 2026/113](https://www.legislation.gov.uk/uksi/2026/113/made) reg. 3(3)(b)), stored in `gov.dwp.universal_credit.rebalancing.protected_health_element`.

- their standard allowance, and
- their health element

rises at least in line with CPI inflation. The model implements that protection through the health element itself, preserving the combined award outcome without separately modelling the small administrative split between protected LCWRA amounts and any under-25 standard allowance supplement.
For a tax year without a legislated amount, the model uses the lowest amount the Universal Credit Act 2025 s. 4 duty allows. For every standard allowance amount, the protected amount plus that allowance must be at least the previous year's sum, increased by the relevant CPI percentage: the CPI 12-month rate in the September before the tax year, never below 0% (s. 4(4)(a)). The model reads that rate from `gov.economic_assumptions.statutory_uprating_inputs.cpi_september`. Section 3 switches off the element's ordinary uprating, so the amount otherwise stays where it was. Run from the 2025-26 amounts at the 3.8% September 2025 CPI rate, this rule gives the legislated £429.80.

The implementation uses transition probabilities based on WPI Economics analysis for the Trussell Trust, derived from administrative Personal Independence Payment data. The probability of being a new claimant varies by year:

Expand All @@ -51,7 +48,7 @@ These uplifts are applied to the CPI-uprated standard allowance for each year. I
The reforms are implemented through parameters, scenario modifiers, and scenarios that work together to enable policy analysis.
```

- **Parameters**: Three YAML files define the reform's activation status, health element amount for new claimants, and standard allowance uplift rates.
- **Parameters**: YAML files define the reform's activation status, the health element amounts for new claimants and for protected (pre-2026) claimants, and the standard allowance uplift rates.
- **Scenario modifier**: The `add_universal_credit_reform` function applies the protected existing-claimant health-element path during microsimulation.
- **Scenario**: The `universal_credit_july_2025_reform` scenario enables the reforms in policy analysis.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
description: Monthly LCWRA element for a pre-2026 claimant, a severe conditions criteria claimant or a claimant who is terminally ill (the protected LCWRA amount). Each dated value is the legislated amount for the tax year it falls in; a later tax year without one takes the lowest amount the Universal Credit Act 2025 s. 4 duty allows.
values:
2026-04-01:
value: 429.80
reference:
- title: The Universal Credit and Employment and Support Allowance (Rates of Allowances) (Amendment) Regulations 2026, reg. 3(3)(b)
href: https://www.legislation.gov.uk/uksi/2026/113/made
- title: Universal Credit Regulations 2013, reg. 36 table (LCWRA element)
href: https://www.legislation.gov.uk/uksi/2013/376/regulation/36
# Not yet legislated: from here each tax year takes the s. 4 floor. The null
# also ends a reform that sets one year, so the next year projects from it.
2027-04-01: null
metadata:
# Keep the statutory dates: uc_reform.py reads a tax year's legislated amount
# and projects the years that have none.
preserve_calendar_dates: true
unit: currency-GBP
period: month
label: Universal Credit protected LCWRA element amount
reference:
- title: Universal Credit Act 2025, s. 4 (increase of the protected LCWRA amount)
href: https://www.legislation.gov.uk/ukpga/2025/22/section/4
124 changes: 79 additions & 45 deletions policyengine_uk/scenarios/uc_reform.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,97 @@
from policyengine_uk.model_api import Scenario
from policyengine_uk import Microsimulation
from policyengine_uk.variables.gov.dwp.universal_credit.standard_allowance.uc_standard_allowance_claimant_type import (
UCClaimantType,
)
import numpy as np


BASELINE_UC_REBALANCING_YEAR = 2025
# The standard allowance amounts the protected LCWRA amount is paired with in
# the Universal Credit Act 2025 s. 4(2) duty: every amount of the allowance.
STANDARD_ALLOWANCE_TYPES = (
"SINGLE_YOUNG",
"SINGLE_OLD",
"COUPLE_YOUNG",
"COUPLE_OLD",
)


def _benefit_uprating_ratio(sim: Microsimulation, year: int) -> float:
parameters = sim.tax_benefit_system.parameters
current_index = float(parameters(str(year)).gov.benefit_uprating_cpi)
baseline_index = float(
parameters(str(BASELINE_UC_REBALANCING_YEAR)).gov.benefit_uprating_cpi
def protected_lcwra_floor(
previous_amount: float,
previous_standard_allowances: dict,
standard_allowances: dict,
cpi_factor: float,
) -> float:
"""The lowest protected LCWRA amount the Universal Credit Act 2025 allows.

s. 4(2): for each standard allowance amount, the protected LCWRA amount
plus that allowance must be at least the previous tax year's sum,
increased by the relevant CPI percentage (never below 0%, s. 4(4)(a)). s. 3
switches off the element's ordinary uprating, so the amount otherwise stays
where it was. All amounts are monthly.
"""
factor = max(float(cpi_factor), 1.0)
required = max(
(previous_amount + previous_standard_allowances[claimant_type]) * factor
- standard_allowances[claimant_type]
for claimant_type in STANDARD_ALLOWANCE_TYPES
)
return current_index / baseline_index
return max(float(previous_amount), required)


def _rebalanced_standard_allowance_monthly(
sim: Microsimulation, year: int, claimant_type: str
) -> float:
current = sim.tax_benefit_system.parameters(str(year))
return float(
current.gov.dwp.universal_credit.standard_allowance.amount[claimant_type]
)
def _standard_allowances_monthly(sim: Microsimulation, year: int) -> dict:
amounts = sim.tax_benefit_system.parameters(
str(year)
).gov.dwp.universal_credit.standard_allowance.amount
return {
claimant_type: float(amounts[claimant_type])
for claimant_type in STANDARD_ALLOWANCE_TYPES
}


def _protected_existing_health_element_monthly(
sim: Microsimulation, year: int
) -> float:
baseline = sim.tax_benefit_system.parameters(str(BASELINE_UC_REBALANCING_YEAR))
protected_combined_award = _benefit_uprating_ratio(sim, year) * (
float(baseline.gov.dwp.universal_credit.standard_allowance.amount.SINGLE_OLD)
+ float(baseline.gov.dwp.universal_credit.elements.disabled.amount)
)
return protected_combined_award - _rebalanced_standard_allowance_monthly(
sim, year, "SINGLE_OLD"
)
def _relevant_cpi_factor(sim: Microsimulation, year: int) -> float:
"""1 + the relevant CPI percentage for a tax year (UC Act 2025 s. 4(4)(a)):
the CPI 12-month rate in the September before it. The 0% floor is applied
in :func:`protected_lcwra_floor`."""
inputs = sim.tax_benefit_system.parameters.gov.economic_assumptions.statutory_uprating_inputs
return 1 + float(inputs.cpi_september(f"{year - 1}-09-01"))


# The date the fiscal-year annualisation samples a parameter at
# (policyengine_uk.utils.parameters).
FISCAL_YEAR_SAMPLE_DATE = "04-30"


def _protected_amount_parameter(sim: Microsimulation):
return sim.tax_benefit_system.parameters.gov.dwp.universal_credit.rebalancing.protected_health_element


def _protected_single_young_health_element_monthly(
def _protected_existing_health_element_monthly(
sim: Microsimulation, year: int
) -> float:
baseline = sim.tax_benefit_system.parameters(str(BASELINE_UC_REBALANCING_YEAR))
protected_combined_award = _benefit_uprating_ratio(sim, year) * (
float(baseline.gov.dwp.universal_credit.standard_allowance.amount.SINGLE_YOUNG)
+ float(baseline.gov.dwp.universal_credit.elements.disabled.amount)
"""The protected LCWRA amount for a tax year, monthly.

UC Regs 2013 reg. 36 gives one amount to every pre-2026, severe conditions
criteria or terminally ill claimant, whatever their age or couple status
(£429.80 for 2026-27, SI 2026/113 reg. 3(3)(b)). A tax year whose amount is
null (not yet legislated) takes the s. 4 floor from the year before; any
other amount, legislated or set by a reform, is used as given.
"""
parameter = _protected_amount_parameter(sim)
amount = parameter(f"{year}-{FISCAL_YEAR_SAMPLE_DATE}")
if amount is not None:
return float(amount)
first = min(
int(entry.instant_str[:4])
for entry in parameter.values_list
if entry.value is not None
)
return protected_combined_award - _rebalanced_standard_allowance_monthly(
sim, year, "SINGLE_YOUNG"
if year <= first:
raise ValueError(
f"No protected LCWRA amount is set for {year} or any earlier year."
)
return protected_lcwra_floor(
_protected_existing_health_element_monthly(sim, year - 1),
_standard_allowances_monthly(sim, year - 1),
_standard_allowances_monthly(sim, year),
_relevant_cpi_factor(sim, year),
)


Expand All @@ -74,18 +116,10 @@ def add_universal_credit_reform(sim: Microsimulation):
# Copy: calculate returns the simulation's cached array, and the writes
# below must reach the cache only through set_input.
current_health_element = np.array(sim.calculate("uc_LCWRA_element", year))
claimant_type = sim.calculate("uc_standard_allowance_claimant_type", year)
has_health_element = current_health_element > 0
protected_health_element = np.full(
current_health_element.shape,
_protected_existing_health_element_monthly(sim, year) * 12,
dtype=current_health_element.dtype,
)
protected_health_element[claimant_type == UCClaimantType.SINGLE_YOUNG.name] = (
_protected_single_young_health_element_monthly(sim, year) * 12
)
# One protected amount for every pre-2026 claimant (reg. 36).
current_health_element[has_health_element & ~is_post_2025_claimant] = (
protected_health_element[has_health_element & ~is_post_2025_claimant]
_protected_existing_health_element_monthly(sim, year) * 12
)
# Set post-April 2026 claimants to £217.26/month.
# https://bills.parliament.uk/publications/62123/documents/6889#page=16
Expand Down
Loading
Loading