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
5 changes: 5 additions & 0 deletions changelog.d/uc-deductions-floor-reconciliation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- Fixed Universal Credit deductions and benefit cap reductions to report what the award actually loses under a protected minimum floor, so the components sum to the reduction applied instead of over-reporting it.
- Added `uc_benefit_cap_reduction`, the benefit cap reduction applied to a Universal Credit award after the protected floor, leaving `benefit_cap_reduction` gross for Housing Benefit.
- Fixed a protected floor above 1 adding money to Universal Credit awards, by clamping the permitted reduction at zero.
- Fixed Universal Credit deductions applying to benefit units that would not claim Universal Credit.
- Corrected the effective date of the 25% deductions cap to 30 April 2021, and the one-penny minimum payable citation to Schedule 6, paragraph 3(1)(a) of SI 2013/380.
16 changes: 11 additions & 5 deletions docs/book/validation/uc-deductions.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ The implementation separates **latent demand** from the **statutory cap**. `uc_l

Enhanced FRS 2023/24 v1.40.3, against [DWP's Universal Credit deductions statistics](https://www.gov.uk/government/statistics/universal-credit-quarterly-statistics-29-april-2013-to-12-february-2026/universal-credit-deductions-statistics-march-2025-to-february-2026).

Shares below are of **all** UC households, not of those with deductions — the denominator both the model and DWP use.

| Statistic | Model | DWP published |
|---|---|---|
| Share of UC households with deductions | 47.0–47.1% | 46–47% |
| At-cap share, 25% regime (2024) | 13.4% | 13–14% (Mar–May 2025) |
| At-cap share, 15% regime (2025) | 26.4% | 21% (Jun 2025–Feb 2026) |
| Above-cap share (last resort) | 1.8% | 2% |
| At-cap share of all UC households, 25% regime (2024) | 13.4% | 13–14% (Mar–May 2025) |
| At-cap share of all UC households, 15% regime (2025) | 26.4% | 21% (Jun 2025–Feb 2026) |
| Above-cap share of all UC households (last resort) | 1.8% | 2% |
| Mean monthly deduction, 25% regime | £66 | £67–68 |
| Mean monthly deduction, 15% regime | £50 | £51–54 |
| Mean annual gain, 25%→15% cap | £421 | £420 (gov.uk) |
Expand Down Expand Up @@ -47,7 +49,11 @@ The latent distribution is observed in 2025 and applied to all model years as a

### The protected floor binds on last resort deductions

The `protected_floor` lever limits combined deductions and benefit cap reductions to (1 − floor) × the standard allowance. It binds on the whole of `uc_deductions`, including the above-cap excess that current law exempts from the deductions cap (last resort and child maintenance deductions). JRF's briefing does not say whether their floor exempts those categories — its worked example involves only cappable deductions and the benefit cap — so this is a modeling choice.
The `protected_floor` lever limits combined deductions and benefit cap reductions to (1 − floor) × the standard allowance. The benefit cap reduction absorbs the floor first: `uc_deductions` is cut only where deductions alone still exceed the allowance. Under the Fair Repayment Rate the 15% deductions cap equals the allowance an 85% floor leaves, so cappable deductions essentially never breach the floor on their own — only benefit cap reductions push past it. JRF's own worked example (standard allowance £92, deduction £14, benefit cap £59, floor £78) works out the same way: the £14 deduction survives in full and the £59 cap reduction goes.

Where deductions alone do exceed the allowance, the floor binds on the whole of `uc_deductions`, including the above-cap excess that current law exempts from the deductions cap (last resort and child maintenance deductions). JRF's briefing does not say whether their floor exempts those categories — its worked example involves only cappable deductions and the benefit cap — so this is a modeling choice.

Both components report what the award actually loses, so `uc_deductions` and `uc_benefit_cap_reduction` sum to the entitlement forgone. `benefit_cap_reduction` itself stays gross: it also drives Housing Benefit, which the Universal Credit floor does not protect. Read `uc_benefit_cap_reduction` for the cap reduction applied to a UC award under a floor reform.

### Other assumptions

Expand Down Expand Up @@ -75,7 +81,7 @@ The `protected_floor` lever limits combined deductions and benefit cap reduction
{"gov.dwp.universal_credit.deductions.protected_floor": {"2026-01-01.2030-12-31": 0.85}}
```

A cap of 1 − *x* is equivalent to a protected minimum floor at *x* of the standard allowance for capped deduction types. The dedicated `protected_floor` lever additionally binds on benefit cap reductions, which is where most of JRF's post-Fair-Repayment-Rate effect comes from.
A cap of 1 − *x* is equivalent to a protected minimum floor at *x* of the standard allowance for capped deduction types. The dedicated `protected_floor` lever additionally binds on benefit cap reductions, which is where most of JRF's post-Fair-Repayment-Rate effect comes from — and, because the cap reduction absorbs the floor first, where nearly all of the modelled effect lands.

## Where the assignment lives

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
description: Maximum total deductions from a Universal Credit award, as a fraction of the standard allowance. Schedule 6 paragraph 4(1) of SI 2013/380 sets a statutory ceiling of 40%; within it, DWP sets the operational cap administratively - 30% from October 2019 (Budget 2018), 25% from April 2021 (announced for October 2021 at Budget 2020, brought forward at Budget 2021), and 15% (the Fair Repayment Rate) from 30 April 2025, which required no legislative change. Last resort deductions (housing and fuel arrears hardship deductions) and child maintenance deductions - separate categories in DWP guidance - may each exceed the cap (Explanatory Memorandum to SI 2025/387, paragraphs 5.10 to 5.15).
description: Maximum total deductions from a Universal Credit award, as a fraction of the standard allowance. Schedule 6 paragraph 4(1) of SI 2013/380 sets a statutory ceiling of 40%; within it, DWP sets the operational cap administratively - 30% from October 2019 (Budget 2018), 25% from 30 April 2021 (announced for October 2021 at Budget 2020, brought forward at Budget 2021), and 15% (the Fair Repayment Rate) from 30 April 2025, which required no legislative change. Last resort deductions (housing and fuel arrears hardship deductions) and child maintenance deductions - separate categories in DWP guidance - may each exceed the cap (Explanatory Memorandum to SI 2025/387, paragraphs 5.10 to 5.15).
values:
2015-04-01: 0.4
2019-10-01: 0.3
2021-04-01: 0.25
2021-04-30: 0.25
2025-04-30: 0.15
metadata:
label: UC deductions cap
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
description: Minimum Universal Credit payable after deductions, per assessment period. Schedule 6 of SI 2013/380 provides that deductions must not reduce the amount payable to the claimant below one penny.
description: Minimum Universal Credit payable after deductions, per assessment period. Schedule 6, paragraph 3(1)(a) of SI 2013/380 provides that deductions must not reduce the amount payable to the claimant below one penny.
values:
2015-04-01: 0.01
metadata:
label: UC minimum payable after deductions
unit: currency-GBP
period: month
reference:
- title: SI 2013/380, Schedule 6 (deductions from benefit and direct payment to third parties)
href: https://www.legislation.gov.uk/uksi/2013/380/schedule/6
- title: SI 2013/380, Schedule 6, paragraph 3(1)(a) (deductions must leave at least one penny payable)
href: https://www.legislation.gov.uk/uksi/2013/380/schedule/6/paragraph/3
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
description: Protected minimum floor in Universal Credit, as a fraction of the standard allowance. When above zero, combined deductions and benefit cap reductions are limited to (1 - floor) times the standard allowance, following JRF's proposal design (their worked example limits the reduction itself to 15% of the standard allowance). Zero - the value under current law, which has no such floor - leaves reductions unlimited. Setting 0.85 models JRF's proposal; this reform-lever parameter encodes no current-law value other than zero. The modeled floor binds on all deductions, including the above-cap excess that current law exempts from the deductions cap (last resort and child maintenance deductions) - JRF's briefing does not state whether their floor exempts those categories, and its worked example involves only cappable deductions and the benefit cap, so treating the floor as binding on everything is a modeling choice rather than a stated feature of the proposal.
description: Protected minimum floor in Universal Credit, as a fraction of the standard allowance. When above zero, combined deductions and benefit cap reductions are limited to (1 - floor) times the standard allowance, following JRF's proposal design (their worked example limits the reduction itself to 15% of the standard allowance). Zero - the value under current law, which has no such floor - leaves reductions unlimited. Setting 0.85 models JRF's proposal; this reform-lever parameter encodes no current-law value other than zero. The benefit cap reduction absorbs the floor first (uc_benefit_cap_reduction), so deductions are limited only where they alone exceed the allowance - matching JRF's worked example, which preserves the deduction in full and eliminates the benefit cap reduction. Where deductions do exceed it, the modeled floor binds on all of them, including the above-cap excess that current law exempts from the deductions cap (last resort and child maintenance deductions) - JRF's briefing does not state whether their floor exempts those categories, and its worked example involves only cappable deductions and the benefit cap, so treating the floor as binding on everything is a modeling choice rather than a stated feature of the proposal.
values:
2015-04-01: 0
metadata:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@
household:
members: person
output:
# Schedule 6 of SI 2013/380: the award cannot be deducted below one
# Schedule 6, paragraph 3(1)(a) of SI 2013/380: the award cannot be
# deducted below one
# penny per assessment period (12p per year).
uc_deductions: 499.88
universal_credit: 0.12
Expand Down
94 changes: 91 additions & 3 deletions policyengine_uk/tests/test_uc_deductions.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ def test_protected_floor_inactive_when_reductions_stay_above_it(self):
)

def test_protected_floor_binds_on_the_above_cap_excess(self):
# Latent demand of 30% is 15% cappable plus a 15% last resort excess
# that current law exempts from the cap, giving a 20% deduction rate
# under the 15% cap. The modeled floor limits combined reductions
# Latent demand of 30% is 25% cappable (cut to 15% by the cap) plus a
# 5% last resort excess that current law exempts from the cap, giving
# a 20% deduction rate. The modeled floor limits combined reductions
# regardless of category, so it cuts into that excess. JRF's briefing
# does not say whether their floor exempts last resort and child
# maintenance deductions; this pins the modeling choice.
Expand All @@ -186,6 +186,94 @@ def test_protected_floor_binds_on_the_above_cap_excess(self):
6_000 - 0.15 * sa
)

def test_floor_components_reconcile_with_the_reduction_applied(self):
# uc_deductions and uc_benefit_cap_reduction are what the award
# actually loses, so they must sum to the entitlement forgone. Before
# the floor was pushed into the components, they reported £960 and
# £1,000 against a £720 reduction actually applied.
situation = make_situation(
benefit_cap_reduction={YEAR: 1_000},
uc_latent_deduction_rate={YEAR: 0.30},
uc_deduction_combination={YEAR: "ADVANCE_ONLY"},
)
reform = Simulation(
situation=situation,
reform={
"gov.dwp.universal_credit.deductions.protected_floor": {
"2025-01-01.2025-12-31": 0.85
}
},
)
sa = reform.calculate("uc_standard_allowance", YEAR)[0]
deductions = reform.calculate("uc_deductions", YEAR)[0]
cap_reduction = reform.calculate("uc_benefit_cap_reduction", YEAR)[0]
universal_credit = reform.calculate("universal_credit", YEAR)[0]
assert deductions + cap_reduction == pytest.approx(0.15 * sa)
assert deductions + cap_reduction == pytest.approx(6_000 - universal_credit)
# The gross benefit cap reduction is unchanged: it also drives
# Housing Benefit, which the Universal Credit floor does not protect.
assert reform.calculate("benefit_cap_reduction", YEAR)[0] == 1_000

def test_the_benefit_cap_reduction_absorbs_the_floor_first(self):
# JRF's worked example (standard allowance £92, deduction £14,
# benefit cap £59, floor £78) keeps the £14 deduction whole and
# eliminates the £59 cap reduction. Deductions of 10% of the standard
# allowance fit inside a 15% floor allowance, so they survive intact
# and the cap reduction takes the whole remaining squeeze.
situation = make_situation(
benefit_cap_reduction={YEAR: 2_500},
uc_latent_deduction_rate={YEAR: 0.10},
uc_deduction_combination={YEAR: "ADVANCE_ONLY"},
)
reform = Simulation(
situation=situation,
reform={
"gov.dwp.universal_credit.deductions.protected_floor": {
"2025-01-01.2025-12-31": 0.85
}
},
)
sa = reform.calculate("uc_standard_allowance", YEAR)[0]
assert reform.calculate("uc_deductions", YEAR)[0] == pytest.approx(0.10 * sa)
assert reform.calculate("uc_benefit_cap_reduction", YEAR)[0] == pytest.approx(
0.05 * sa
)

def test_a_floor_above_the_standard_allowance_adds_nothing(self):
# (1 - floor) x standard allowance goes negative above a floor of 1;
# subtracting it added £960 to a £6,000 award. The allowance clamps
# at zero, where the floor protects the standard allowance entirely.
situation = make_situation(
benefit_cap_reduction={YEAR: 1_000},
uc_latent_deduction_rate={YEAR: 0.25},
uc_deduction_combination={YEAR: "ADVANCE_ONLY"},
)
reform = Simulation(
situation=situation,
reform={
"gov.dwp.universal_credit.deductions.protected_floor": {
"2025-01-01.2025-12-31": 1.2
}
},
)
assert reform.calculate("uc_deductions", YEAR)[0] == 0
assert reform.calculate("uc_benefit_cap_reduction", YEAR)[0] == 0
assert reform.calculate("universal_credit", YEAR)[0] == pytest.approx(6_000)

def test_no_deductions_without_a_universal_credit_claim(self):
# Datasets are meant to impute uc_latent_deduction_rate directly, at
# which point nothing else gates deductions on the claim.
sim = Simulation(
situation=make_situation(
would_claim_uc={YEAR: False},
uc_latent_deduction_rate={YEAR: 0.30},
uc_deduction_combination={YEAR: "ADVANCE_ONLY"},
)
)
assert sim.calculate("universal_credit", YEAR)[0] == 0
assert sim.calculate("uc_deductions", YEAR)[0] == 0
assert sim.calculate("uc_benefit_cap_reduction", YEAR)[0] == 0

def test_zeroing_every_type_share_leaves_a_valid_enum_array(self):
situation = make_situation(
uc_deduction_random_draw={YEAR: 0.0},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ class uc_deductions(Variable):
"Money deducted from this benefit unit's Universal Credit award to "
"repay debts: advance repayments, government debt (benefit and tax "
"credit overpayments) and third party deductions. Deductions leave "
"at least one penny per assessment period payable (Schedule 6 of "
"SI 2013/380). Per-household statistics are validated against the "
"DWP deductions statistics; weighted aggregates are not, because "
"they scale with the model's UC caseload, which falls short of the "
"administrative count."
"at least one penny per assessment period payable (Schedule 6, "
"paragraph 3(1)(a) of SI 2013/380). Per-household statistics are "
"validated against the DWP deductions statistics; weighted "
"aggregates are not, because they scale with the model's UC "
"caseload, which falls short of the administrative count."
)
entity = BenUnit
definition_period = YEAR
value_type = float
unit = GBP
defined_for = "would_claim_uc"

def formula(benunit, period, parameters):
p = parameters(period).gov.dwp.universal_credit.deductions
Expand All @@ -28,4 +29,17 @@ def formula(benunit, period, parameters):
0,
)
minimum_payable = p.minimum_payable * MONTHS_IN_YEAR
return min_(rate * standard_allowance, max_(award - minimum_payable, 0))
deductions = min_(rate * standard_allowance, max_(award - minimum_payable, 0))
# The protected minimum floor (zero, and so inactive, under current
# law) limits deductions and benefit cap reductions combined. The
# benefit cap reduction absorbs it first, so deductions bend only
# where they alone exceed the floor allowance: under the Fair
# Repayment Rate the 15% deductions cap equals a typical 15% floor
# allowance, so cappable deductions essentially never breach the
# floor on their own. The floor does bind on the above-cap excess
# that current law exempts from the deductions cap (last resort and
# child maintenance deductions). JRF's briefing does not say whether
# their floor exempts those categories - its worked example involves
# only cappable deductions and the benefit cap - so this is a
# modeling choice, tracked for follow-up.
return min_(deductions, benunit("uc_maximum_reduction", period))
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from policyengine_uk.model_api import *


class uc_maximum_reduction(Variable):
label = "UC maximum combined reduction"
documentation = (
"Most that benefit cap reductions and deductions combined may take "
"off a Universal Credit award under the protected minimum floor: "
"(1 - floor) x the standard allowance. Infinite when the floor is "
"zero - the value under current law, which has no such floor - so "
"reductions are unlimited."
)
entity = BenUnit
definition_period = YEAR
value_type = float
unit = GBP

def formula(benunit, period, parameters):
# A protected minimum floor (zero under current law) caps combined
# benefit cap reductions and deductions at (1 - floor) x the standard
# allowance, per JRF's protected minimum floor design (their worked
# example limits the reduction itself to 15% of the standard
# allowance). Zero means reductions are unlimited.
floor_rate = parameters(
period
).gov.dwp.universal_credit.deductions.protected_floor
standard_allowance = benunit("uc_standard_allowance", period)
# A floor above 1 would make the allowance negative; subtracting a
# negative reduction would add money to the award. Clamp at zero,
# where the floor protects the whole standard allowance.
return where(
floor_rate > 0,
max_((1 - floor_rate) * standard_allowance, 0),
np.inf,
)
Loading