From caa1c6e5b70ddfe0f06a489f7bcee512e2556820 Mon Sep 17 00:00:00 2001 From: vahid-ahmadi Date: Thu, 13 Aug 2026 12:44:00 +0100 Subject: [PATCH 1/2] Reconcile UC deduction and benefit cap components under the protected floor The protected minimum floor was applied only when computing universal_credit, so uc_deductions and benefit_cap_reduction reported their unfloored values: a floor of 0.85 with a 1,000 GBP benefit cap reduction and a 0.30 latent rate reported 960.34 + 1,000 GBP against a 720.25 GBP reduction actually applied. The floor now lives in the components. uc_maximum_reduction holds the allowance the floor leaves; uc_deductions is limited only where deductions alone exceed it, and the new uc_benefit_cap_reduction takes what remains. That ordering matches JRF's worked example, which preserves the deduction and eliminates the cap reduction, and it means the components sum to the entitlement forgone. benefit_cap_reduction stays gross because Housing Benefit uses it and the UC floor does not protect Housing Benefit, which also keeps it out of a dependency cycle. Also clamps the floor allowance at zero (a floor above 1 made it negative and added money to the award), gates uc_deductions on would_claim_uc for the dataset-imputed migration path, and corrects the 25% cap date to 30 April 2021 and the one-penny citation to Schedule 6 paragraph 3(1)(a) of SI 2013/380. Current-law results are unchanged: the floor is zero, so the allowance is infinite and both components pass through unaltered. Co-Authored-By: Claude Opus 5 (1M context) --- .../uc-deductions-floor-reconciliation.md | 5 + docs/book/validation/uc-deductions.md | 16 +++- .../dwp/universal_credit/deductions/cap.yaml | 4 +- .../deductions/minimum_payable.yaml | 6 +- .../deductions/protected_floor.yaml | 2 +- policyengine_uk/tests/test_uc_deductions.py | 94 ++++++++++++++++++- .../deductions/uc_deductions.py | 26 +++-- .../deductions/uc_maximum_reduction.py | 35 +++++++ .../uc_benefit_cap_reduction.py | 36 +++++++ .../dwp/universal_credit/universal_credit.py | 30 ++---- 10 files changed, 212 insertions(+), 42 deletions(-) create mode 100644 changelog.d/uc-deductions-floor-reconciliation.md create mode 100644 policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_maximum_reduction.py create mode 100644 policyengine_uk/variables/gov/dwp/universal_credit/uc_benefit_cap_reduction.py diff --git a/changelog.d/uc-deductions-floor-reconciliation.md b/changelog.d/uc-deductions-floor-reconciliation.md new file mode 100644 index 000000000..b60d47e56 --- /dev/null +++ b/changelog.d/uc-deductions-floor-reconciliation.md @@ -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. diff --git a/docs/book/validation/uc-deductions.md b/docs/book/validation/uc-deductions.md index 5d7d4fc4a..c84be0551 100644 --- a/docs/book/validation/uc-deductions.md +++ b/docs/book/validation/uc-deductions.md @@ -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) | @@ -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 @@ -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 diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/cap.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/cap.yaml index 9a62d98a8..7cc392ffb 100644 --- a/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/cap.yaml +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/cap.yaml @@ -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 diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/minimum_payable.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/minimum_payable.yaml index 075b067fe..47e5da327 100644 --- a/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/minimum_payable.yaml +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/minimum_payable.yaml @@ -1,4 +1,4 @@ -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: @@ -6,5 +6,5 @@ metadata: 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 diff --git a/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/protected_floor.yaml b/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/protected_floor.yaml index 50fbf3aca..0da2c48b4 100644 --- a/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/protected_floor.yaml +++ b/policyengine_uk/parameters/gov/dwp/universal_credit/deductions/protected_floor.yaml @@ -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: diff --git a/policyengine_uk/tests/test_uc_deductions.py b/policyengine_uk/tests/test_uc_deductions.py index ffab86dfe..373d79894 100644 --- a/policyengine_uk/tests/test_uc_deductions.py +++ b/policyengine_uk/tests/test_uc_deductions.py @@ -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. @@ -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}, diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_deductions.py b/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_deductions.py index 6425ae533..05ed24690 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_deductions.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_deductions.py @@ -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 @@ -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)) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_maximum_reduction.py b/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_maximum_reduction.py new file mode 100644 index 000000000..82e37fa76 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_maximum_reduction.py @@ -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, + ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/uc_benefit_cap_reduction.py b/policyengine_uk/variables/gov/dwp/universal_credit/uc_benefit_cap_reduction.py new file mode 100644 index 000000000..081dc56c6 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/uc_benefit_cap_reduction.py @@ -0,0 +1,36 @@ +from policyengine_uk.model_api import * + + +class uc_benefit_cap_reduction(Variable): + label = "UC benefit cap reduction" + documentation = ( + "Benefit cap reduction actually taken off this benefit unit's " + "Universal Credit award. Equal to benefit_cap_reduction under " + "current law. Where a protected minimum floor is in force, the cap " + "reduction absorbs the floor first, so only the part of it fitting " + "in the allowance uc_deductions leaves is applied. " + "benefit_cap_reduction itself stays gross, because it also applies " + "to Housing Benefit, which the Universal Credit floor does not " + "protect." + ) + entity = BenUnit + definition_period = YEAR + value_type = float + unit = GBP + defined_for = "would_claim_uc" + + def formula(benunit, period, parameters): + reduction = benunit("benefit_cap_reduction", period) + # Deductions take their share of the floor allowance first, so the + # cap reduction is what bends when the floor binds. Under the Fair + # Repayment Rate the 15% deductions cap equals a typical 15% floor + # allowance, so cappable deductions alone essentially never breach + # the floor - only benefit cap reductions push past it. JRF's worked + # example (standard allowance £92, deduction £14, benefit cap £59, + # floor £78) preserves the £14 deduction and eliminates the £59 cap + # reduction, which is this ordering. + headroom = max_( + benunit("uc_maximum_reduction", period) - benunit("uc_deductions", period), + 0, + ) + return min_(reduction, headroom) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/universal_credit.py b/policyengine_uk/variables/gov/dwp/universal_credit/universal_credit.py index f30621334..5230ba632 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/universal_credit.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/universal_credit.py @@ -11,26 +11,12 @@ class universal_credit(Variable): def formula(benunit, period, parameters): uc_max_entitlement = benunit("universal_credit_pre_benefit_cap", period) - benefit_cap_reduction = benunit("benefit_cap_reduction", period) + # Both components are already limited by the protected minimum floor + # (zero, and so inactive, under current law): uc_deductions takes its + # share of the floor allowance first and uc_benefit_cap_reduction + # takes what is left. Subtracting them separately, rather than + # limiting their sum here, keeps the reported components summing to + # the reduction actually applied. + benefit_cap_reduction = benunit("uc_benefit_cap_reduction", period) deductions = benunit("uc_deductions", period) - # 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. - # 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, tracked for follow-up. - floor_rate = parameters( - period - ).gov.dwp.universal_credit.deductions.protected_floor - total_reductions = benefit_cap_reduction + deductions - max_reductions = where( - floor_rate > 0, - (1 - floor_rate) * benunit("uc_standard_allowance", period), - np.inf, - ) - return max_(uc_max_entitlement - min_(total_reductions, max_reductions), 0) + return max_(uc_max_entitlement - benefit_cap_reduction - deductions, 0) From d287b189d3f5c068f084d910aa8fc7f54aae68ea Mon Sep 17 00:00:00 2001 From: vahid-ahmadi Date: Thu, 13 Aug 2026 12:45:07 +0100 Subject: [PATCH 2/2] Cite Schedule 6 paragraph 3(1)(a) in the one-penny policy test comment Co-Authored-By: Claude Opus 5 (1M context) --- .../finance/benefit/family/universal_credit/uc_deductions.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/uc_deductions.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/uc_deductions.yaml index 1840c1391..23e9f370d 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/uc_deductions.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/uc_deductions.yaml @@ -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