Skip to content
Open
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/uc-rebalancing-protection.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Corrected Universal Credit rebalancing so existing health-element claimants keep their combined standard allowance and health element award CPI-protected.
4 changes: 2 additions & 2 deletions docs/book/policy/model-baseline.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ The government increased the [employer National Insurance rate from 13.8% to 15%

### Universal Credit rebalancing

Parliament passed legislation to implement Universal Credit rebalancing reforms, with the [rebalancing switch activated in fiscal year 2025-26](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/active.yaml#L3). The reforms include [graduated standard allowance uplifts](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/standard_allowance_uplift.yaml) above inflation: 2.3% in 2026-27, 3.1% in 2027-28, 4.0% in 2028-29, and 4.8% in 2029-30. A [new health element of £217.26](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/new_claimant_health_element.yaml#L3) will be introduced for new claimants in fiscal year 2026-27.
Parliament passed legislation to implement Universal Credit rebalancing reforms, with the [rebalancing switch activated in fiscal year 2025-26](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/active.yaml#L3). The reforms include [graduated standard allowance uplifts](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/standard_allowance_uplift.yaml) above inflation: 2.3% in 2026-27, 3.1% in 2027-28, 4.0% in 2028-29, and 4.8% in 2029-30. A [new health element of £217.26](https://github.com/PolicyEngine/policyengine-uk/blob/master/policyengine_uk/parameters/gov/dwp/universal_credit/rebalancing/new_claimant_health_element.yaml#L3) applies to most new claimants in fiscal year 2026-27, while existing health-element claimants keep the combined value of their standard allowance and health element at least in line with CPI.

### Benefit uprating

Expand Down Expand Up @@ -113,4 +113,4 @@ Notable exclusions:
- **Non-UK resident stamp duty surcharge**: 2% additional rate from fiscal year 2021-22 is not modelled
- **Some devolved tax policies**: Beyond property transaction taxes, other devolved policies may have limited coverage

All parameter values include references to primary legislation and can be found in the [PolicyEngine UK parameters directory](https://github.com/PolicyEngine/policyengine-uk/tree/master/policyengine_uk/parameters).
All parameter values include references to primary legislation and can be found in the [PolicyEngine UK parameters directory](https://github.com/PolicyEngine/policyengine-uk/tree/master/policyengine_uk/parameters).
27 changes: 20 additions & 7 deletions docs/book/policy/uc-rebalancing.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,25 @@ The Universal Credit rebalancing reforms represent changes to Universal Credit p
## Overview

```{important}
The reforms consist of two main components: health element changes for new claimants and standard allowance uplifts.
The reforms combine a higher standard allowance, protected awards for existing health-element recipients, and a lower fixed health element for most new claimants.
```

1. **Health element changes for new claimants**: New Universal Credit claimants from April 2026 onwards receive a fixed health element amount, while existing claimants continue to receive inflation-linked increases.
1. **Protected awards for existing claimants**: Existing recipients of the health element keep the combined value of their standard allowance and health element at least in line with CPI inflation through 2029-30.

2. **Standard allowance uplifts**: The standard allowance receives additional uplifts beyond the annual inflationary increase from 2026-2029.
2. **Health element changes for new claimants**: New Universal Credit claimants from April 2026 onwards receive a fixed monthly health element amount of £217.26, rather than the protected existing-claimant amount.

3. **Standard allowance uplifts**: The standard allowance receives additional uplifts beyond the annual inflationary increase from 2026-2029.

## Health element changes

From April 2026, new Universal Credit claimants who qualify for the Limited Capacity for Work-Related Activity (LCWRA) element receive a fixed monthly amount of £217.26, rather than the inflation-adjusted amount that pre-2026 claimants continue to receive.
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:

- 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.

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 @@ -34,7 +43,7 @@ The standard allowance receives additional percentage uplifts beyond the normal
- 2028: 4.0% additional uplift (cumulative)
- 2029: 4.8% additional uplift (cumulative)

These uplifts are applied to the previous year's standard allowance amount and compound over time.
These uplifts are applied to the CPI-uprated standard allowance for each year. In other words, the model first applies the usual CPI uprating and then applies the rebalancing uplift on top.

## Implementation

Expand All @@ -43,7 +52,7 @@ The reforms are implemented through parameters, scenario modifiers, and scenario
```

- **Parameters**: Three YAML files define the reform's activation status, health element amount for new claimants, and standard allowance uplift rates.
- **Scenario modifier**: The `add_universal_credit_reform` function applies the changes to Universal Credit calculations during microsimulation.
- **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.

## Examples
Expand Down Expand Up @@ -98,4 +107,8 @@ sim = Simulation(scenario=scenario)

## Legislative reference

The reforms are based on provisions in the Universal Credit Bill, available at: https://bills.parliament.uk/publications/62123/documents/6889.
The reforms are based on the Universal Credit Bill and its impact assessment:

- https://bills.parliament.uk/publications/62123/documents/6889
- https://bills.parliament.uk/publications/62124/documents/6892
- https://assets.publishing.service.gov.uk/media/689ca49e1c63de6de5bb1298/withdrawn-universal-credit-bill-uc-rebalancing-impact-assessment.pdf
12 changes: 5 additions & 7 deletions docs/book/usage/scenarios.md
Original file line number Diff line number Diff line change
Expand Up @@ -391,14 +391,14 @@ for year in [2025, 2027, 2029]:

### Building Universal Credit scenarios with dynamic changes

Some scenarios need to make changes that depend on the simulation's own data. Here's how to create a UC scenario that adjusts payments based on claimant characteristics:
Some scenarios need to make changes that depend on the simulation's own data. Here's how to create a UC scenario that adjusts health-element payments based on claimant characteristics:

```python
from policyengine_uk import Scenario, Microsimulation
import numpy as np

def modify_uc_for_new_claimants(sim: Microsimulation):
"""Reduce health elements for new UC claimants while increasing standard allowances"""
"""Reduce health elements for new UC claimants while preserving claimant protections"""
# Access the parameter system to check if reforms are active
rebalancing_params = sim.tax_benefit_system.parameters.gov.dwp.universal_credit.rebalancing

Expand Down Expand Up @@ -434,11 +434,9 @@ def modify_uc_for_new_claimants(sim: Microsimulation):

sim.set_input("uc_LCWRA_element", year, current_health_element)

# Increase standard allowances for everyone
uplift_rate = rebalancing_params.standard_allowance_uplift(year)
previous_allowance = sim.calculate("uc_standard_allowance", year - 1)
new_allowance = previous_allowance * (1 + uplift_rate)
sim.set_input("uc_standard_allowance", year, new_allowance)
# General standard allowance uplifts are already handled in the
# uc_standard_allowance formula. Scenario modifiers only need to add
# claimant-specific overrides such as protected health elements.

# Create the UC rebalancing scenario
uc_rebalancing = Scenario(simulation_modifier=modify_uc_for_new_claimants)
Expand Down
75 changes: 71 additions & 4 deletions policyengine_uk/scenarios/uc_reform.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,69 @@
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


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
)
return current_index / baseline_index


def _rebalanced_standard_allowance_monthly(
sim: Microsimulation, year: int, claimant_type: str
) -> float:
current = sim.tax_benefit_system.parameters(str(year))
standard_allowance = float(
current.gov.dwp.universal_credit.standard_allowance.amount[claimant_type]
)
uplift = float(
current.gov.dwp.universal_credit.rebalancing.standard_allowance_uplift
)
return standard_allowance * (1 + uplift)


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 _protected_single_young_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)
)
return protected_combined_award - _rebalanced_standard_allowance_monthly(
sim, year, "SINGLE_YOUNG"
)


def add_universal_credit_reform(sim: Microsimulation):
rebalancing = sim.tax_benefit_system.parameters.gov.dwp.universal_credit.rebalancing

generator = np.random.default_rng(43)

uc_seed = generator.random(len(sim.calculate("benunit_id")))
p_uc_post_2026_status = {
post_2025_claimant_share = {
2025: 0,
2026: 0.11,
2027: 0.13,
Expand All @@ -20,11 +74,24 @@ def add_universal_credit_reform(sim: Microsimulation):
for year in range(2026, 2030):
if not rebalancing.active(year):
continue
is_post_25_claimant = uc_seed < p_uc_post_2026_status[year]
is_post_2025_claimant = uc_seed < post_2025_claimant_share[year]
current_health_element = sim.calculate("uc_LCWRA_element", year)
# Set new claimants to £217.26/month from April 2026 (pre-2026 claimaints keep inflation-linked increases)
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
)
current_health_element[has_health_element & ~is_post_2025_claimant] = (
protected_health_element[has_health_element & ~is_post_2025_claimant]
)
# Set post-April 2026 claimants to £217.26/month.
# https://bills.parliament.uk/publications/62123/documents/6889#page=16
current_health_element[(current_health_element > 0) & is_post_25_claimant] = (
current_health_element[has_health_element & is_post_2025_claimant] = (
new_claimant_health_element(year) * 12
) # Monthly amount * 12
sim.set_input("uc_LCWRA_element", year, current_health_element)
Expand Down
123 changes: 123 additions & 0 deletions policyengine_uk/tests/test_uc_rebalancing.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import numpy as np
import pytest

import policyengine_uk.scenarios.uc_reform as uc_reform
from policyengine_uk import Simulation

YEARS = range(2025, 2030)


def _uc_claimant(age_2025: int) -> dict:
return {
"people": {
"person": {
"age": {year: age_2025 + year - 2025 for year in YEARS},
"employment_income": {year: 0 for year in YEARS},
"uc_limited_capability_for_WRA": {year: True for year in YEARS},
}
},
"benunits": {"benunit": {"members": ["person"]}},
"households": {"household": {"members": ["person"]}},
}


class _FixedRng:
def __init__(self, values):
self.values = np.array(values, dtype=float)

def random(self, size):
assert size == len(self.values)
return self.values


def _force_uc_seed(monkeypatch, values):
monkeypatch.setattr(
uc_reform.np.random, "default_rng", lambda seed: _FixedRng(values)
)


def _benefit_uprating_factor(sim: Simulation, year: int) -> float:
parameters = sim.tax_benefit_system.parameters
current_index = float(parameters(str(year)).gov.benefit_uprating_cpi)
baseline_index = float(parameters("2025").gov.benefit_uprating_cpi)
return current_index / baseline_index


def _rebalanced_standard_allowance_monthly(
sim: Simulation, year: int, claimant_type: str
) -> float:
current = sim.tax_benefit_system.parameters(str(year))
standard_allowance = float(
current.gov.dwp.universal_credit.standard_allowance.amount[claimant_type]
)
uplift = float(
current.gov.dwp.universal_credit.rebalancing.standard_allowance_uplift
)
return standard_allowance * (1 + uplift)


def _cpi_protected_uc_award_monthly(
sim: Simulation, year: int, claimant_type: str
) -> float:
baseline = sim.tax_benefit_system.parameters("2025").gov.dwp.universal_credit
baseline_standard_allowance = float(
baseline.standard_allowance.amount[claimant_type]
)
baseline_health_element = float(baseline.elements.disabled.amount)
return _benefit_uprating_factor(sim, year) * (
baseline_standard_allowance + baseline_health_element
)


@pytest.mark.parametrize("age_2025", [20, 30])
def test_existing_claimants_keep_combined_award_cpi_protected(monkeypatch, age_2025):
_force_uc_seed(monkeypatch, [0.99])
sim = Simulation(situation=_uc_claimant(age_2025))
claimant_type = "SINGLE_YOUNG" if age_2025 < 25 else "SINGLE_OLD"

for year in range(2026, 2030):
standard_allowance = sim.calculate("uc_standard_allowance", year)[0] / 12
health_element = sim.calculate("uc_LCWRA_element", year)[0] / 12
new_claimant_health_element = float(
sim.tax_benefit_system.parameters(
str(year)
).gov.dwp.universal_credit.rebalancing.new_claimant_health_element
)

assert health_element > new_claimant_health_element
assert standard_allowance + health_element == pytest.approx(
_cpi_protected_uc_award_monthly(sim, year, claimant_type)
)


def test_new_claimants_use_fixed_health_element(monkeypatch):
_force_uc_seed(monkeypatch, [0.0])
sim = Simulation(situation=_uc_claimant(30))

for year in range(2026, 2030):
health_element = sim.calculate("uc_LCWRA_element", year)[0] / 12
expected_health = float(
sim.tax_benefit_system.parameters(
str(year)
).gov.dwp.universal_credit.rebalancing.new_claimant_health_element
)

assert health_element == pytest.approx(expected_health)


def test_standard_allowance_reforms_still_change_standard_allowance(monkeypatch):
_force_uc_seed(monkeypatch, [0.99])
baseline = Simulation(situation=_uc_claimant(30))
reformed = Simulation(
situation=_uc_claimant(30),
reform={
"gov.dwp.universal_credit.standard_allowance.amount.SINGLE_OLD": {
"2025-01-01.2100-12-31": 800
}
},
)

baseline_standard_allowance = baseline.calculate("uc_standard_allowance", 2026)[0]
reformed_standard_allowance = reformed.calculate("uc_standard_allowance", 2026)[0]

assert reformed_standard_allowance / baseline_standard_allowance > 1.5
Loading