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/2739.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Universal Credit deductions count a supplied latent deduction rate only where `uc_has_deduction` is true, as the rate's own formula does. Under a protected floor reform, the flag reads the award left after the benefit cap as the floor limits it.
4 changes: 4 additions & 0 deletions docs/book/validation/uc-deductions.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,7 @@ A cap of 1 − *x* is equivalent to a protected minimum floor at *x* of the stan
Statutory parameters — the cap, the protected floor, the minimum payable penny, the abolition switches — live under `gov.dwp.universal_credit.deductions`. The calibrated distributions live under `gov.simulation.uc_deductions`: they describe the world, not the law.

The assignment formulas are an explicit fallback. The end state imputes `uc_latent_deduction_rate` and `uc_deduction_combination` at dataset build, at which point the model consumes them as plain inputs and the fallback retires; raw-FRS users keep working through the fallback until then. Assignment uses deterministic splitmix64 hashes of `benunit_id`, reproducible across runs and machines, and overridable by datasets or situations through `uc_deduction_random_draw` and `uc_deduction_type_random_draw`. Every simulation built from data gets the hashed draws, including a constituency or local authority filtered from the national data, and a benefit unit gets the same draw there as in the national run. Household situations get no deductions unless set explicitly.

`uc_has_deduction` decides which benefit units have deductions: a unit claiming Universal Credit, with an award left after the benefit cap that carries deductions (`uc_award_carries_deductions`), whose incidence draw falls below its region's incidence. The latent rate counts only for those units, whether the formula assigns it or a dataset or situation supplies it, so a supplied rate follows the same rule as the formula. A dataset that stores latent rates therefore needs the draws that assign them, and Microcosm stores both. On such a dataset, a reform that lowers incidence drops the stored rates of the units the flag no longer assigns. One that raises it flags more units, but the dataset stores no rate for them, so it adds no deductions. A unit that keeps the flag keeps its stored rate, while the formula rescales each draw by the new incidence, so stored rates don't cost an incidence reform in full. To give a household situation deductions, set `uc_has_deduction` to true with `uc_latent_deduction_rate` and `uc_deduction_combination`. Alternatively, supply the advance cash (`uc_advance_repayment_amount`) or the rate itself (`uc_deduction_rate`). Those two describe a known deduction, so they apply whatever the flag says.

A protected floor reform limits what the benefit cap and deductions together take to the floor allowance (`uc_maximum_reduction`), so a family that the cap takes to nil under current law can keep an award. The flag reads the award left after the cap as the floor limits it. Such a family can therefore have deductions, which the floor meets first, as for any other family. Under current law the floor is zero, and the flag reads the award left after the full cap.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
universal_credit_pre_benefit_cap: 6_000
uc_benefit_cap_reduction: 0
uc_latent_deduction_rate: 0.25
uc_has_deduction: true
uc_deduction_combination: ADVANCE_ONLY
households:
household:
Expand All @@ -56,6 +57,7 @@
universal_credit_pre_benefit_cap: 6_000
uc_benefit_cap_reduction: 0
uc_latent_deduction_rate: 0.25
uc_has_deduction: true
uc_deduction_combination: ADVANCE_ONLY
households:
household:
Expand All @@ -79,6 +81,7 @@
universal_credit_pre_benefit_cap: 6_000
uc_benefit_cap_reduction: 0
uc_latent_deduction_rate: 0.28
uc_has_deduction: true
uc_deduction_combination: GOVERNMENT_ONLY
households:
household:
Expand All @@ -102,6 +105,7 @@
universal_credit_pre_benefit_cap: 500
uc_benefit_cap_reduction: 0
uc_latent_deduction_rate: 0.25
uc_has_deduction: true
uc_deduction_combination: ADVANCE_ONLY
households:
household:
Expand Down Expand Up @@ -165,6 +169,7 @@
would_claim_uc: false
would_claim_uc_at_legacy_closure: true
uc_latent_deduction_rate: 0.25
uc_has_deduction: true
uc_deduction_combination: ADVANCE_ONLY
households:
household:
Expand All @@ -179,3 +184,199 @@
uc_benefit_cap_reduction_after_floor: 27_355.24
uc_deductions: 1_130.58
universal_credit: 20_146.82

- name: A supplied latent rate alone gives a household situation no deductions
# The latent rate counts only where uc_has_deduction is true (#2739). A
# household situation's incidence draw defaults to 1.0, so the flag is
# false unless set.
period: 2025
absolute_error_margin: 0.01
input:
people:
person:
age: 30
benunits:
benunit:
members: person
would_claim_uc: true
universal_credit_pre_benefit_cap: 6_000
uc_benefit_cap_reduction: 0
uc_latent_deduction_rate: 0.25
uc_deduction_combination: ADVANCE_ONLY
households:
household:
members: person
output:
uc_has_deduction: false
uc_deduction_rate: 0
uc_deductions: 0
universal_credit: 6_000

- name: A supplied latent rate gives no deductions without the flag under a protected floor
# The example from #2739. The benefit cap takes the whole £1,000 award, so
# an 85% floor binds. With the flag false, the latent rate counts as zero,
# and the cap reduction takes the whole floor allowance:
# 0.15 x 400.14 x 12 = 720.25, leaving 1,000 - 720.25 = 279.75.
period: 2025
absolute_error_margin: 0.01
input:
gov.dwp.universal_credit.deductions.protected_floor: 0.85
people:
person:
age: 30
benunits:
benunit:
members: person
would_claim_uc: true
universal_credit_pre_benefit_cap: 1_000
uc_benefit_cap_reduction: 1_000
uc_latent_deduction_rate: 0.30
uc_deduction_combination: ADVANCE_ONLY
uc_has_deduction: false
households:
household:
members: person
output:
uc_maximum_reduction: 720.25
uc_deduction_rate: 0
uc_deductions: 0
uc_benefit_cap_reduction_after_floor: 720.25
universal_credit: 279.75

- name: The same family with the flag set repays its deductions first under the floor
# Demand is the 15% cap on 25% plus a 5% last resort excess: 0.20 x 4,801.68
# = 960.34. The floor allowance of 720.25 limits it, and the cap reduction
# takes nothing more. The award is 279.75 either way.
period: 2025
absolute_error_margin: 0.01
input:
gov.dwp.universal_credit.deductions.protected_floor: 0.85
people:
person:
age: 30
benunits:
benunit:
members: person
would_claim_uc: true
universal_credit_pre_benefit_cap: 1_000
uc_benefit_cap_reduction: 1_000
uc_latent_deduction_rate: 0.30
uc_deduction_combination: ADVANCE_ONLY
uc_has_deduction: true
households:
household:
members: person
output:
uc_deduction_rate: 0.20
uc_deductions: 720.25
uc_benefit_cap_reduction_after_floor: 0
universal_credit: 279.75

- name: Under a protected floor a family the cap takes to nil can be flagged for deductions
# An incidence draw of 0.1 falls below London's incidence (0.467 x 0.94).
# The 85% floor limits the cap to 720.25, which leaves 279.75 of the
# £1,000 award for deductions to come from, so the flag is true.
period: 2025
absolute_error_margin: 0.01
input:
gov.dwp.universal_credit.deductions.protected_floor: 0.85
people:
person:
age: 30
benunits:
benunit:
members: person
would_claim_uc: true
universal_credit_pre_benefit_cap: 1_000
uc_benefit_cap_reduction: 1_000
uc_deduction_random_draw: 0.1
uc_latent_deduction_rate: 0.30
uc_deduction_combination: ADVANCE_ONLY
households:
household:
members: person
output:
uc_has_deduction: true
uc_deductions: 720.25
uc_benefit_cap_reduction_after_floor: 0
universal_credit: 279.75

- name: Under current law the cap leaves that family no award and no deductions
# Without a floor the cap takes the whole award, so the flag is false.
period: 2025
absolute_error_margin: 0.01
input:
people:
person:
age: 30
benunits:
benunit:
members: person
would_claim_uc: true
universal_credit_pre_benefit_cap: 1_000
uc_benefit_cap_reduction: 1_000
uc_deduction_random_draw: 0.1
uc_latent_deduction_rate: 0.30
uc_deduction_combination: ADVANCE_ONLY
households:
household:
members: person
output:
uc_has_deduction: false
uc_deductions: 0
uc_benefit_cap_reduction_after_floor: 1_000
universal_credit: 0

- name: A floor that leaves a family the cap takes to nil no award leaves it without the flag
# The 85% floor allowance, 0.15 x 400.14 x 12 = 720.25, exceeds the £500
# award, so the cap still takes all of it and the floor does not bind.
# Nothing is left for deductions to come from.
period: 2025
absolute_error_margin: 0.01
input:
gov.dwp.universal_credit.deductions.protected_floor: 0.85
people:
person:
age: 30
benunits:
benunit:
members: person
would_claim_uc: true
universal_credit_pre_benefit_cap: 500
uc_benefit_cap_reduction: 1_000
uc_deduction_random_draw: 0.1
uc_latent_deduction_rate: 0.30
uc_deduction_combination: ADVANCE_ONLY
households:
household:
members: person
output:
uc_maximum_reduction: 720.25
uc_has_deduction: false
uc_deductions: 0
uc_benefit_cap_reduction_after_floor: 500
universal_credit: 0

- name: An explicitly supplied advance amount applies whatever the flag says
# uc_advance_repayment_amount describes a known advance, so the flag does
# not gate it.
period: 2025
absolute_error_margin: 0.01
input:
people:
person:
age: 30
benunits:
benunit:
members: person
would_claim_uc: true
universal_credit_pre_benefit_cap: 6_000
uc_benefit_cap_reduction: 0
uc_advance_repayment_amount: 360
uc_has_deduction: false
households:
household:
members: person
output:
uc_deductions: 360
universal_credit: 5_640
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
would_claim_uc: true
universal_credit_pre_benefit_cap: 6_000
uc_latent_deduction_rate: 0.05
uc_has_deduction: true
uc_deduction_combination: THIRD_PARTY_ONLY
gov.dwp.universal_credit.standard_allowance.amount.SINGLE_OLD: 440
output:
Expand All @@ -48,6 +49,7 @@
universal_credit_pre_benefit_cap: 6_000
uc_advance_repayment_amount: 600
uc_latent_deduction_rate: 0.05
uc_has_deduction: true
uc_deduction_combination: THIRD_PARTY_ONLY
gov.dwp.universal_credit.standard_allowance.amount.SINGLE_OLD: 440
output:
Expand Down Expand Up @@ -86,6 +88,7 @@
would_claim_uc: true
universal_credit_pre_benefit_cap: 6_000
uc_latent_deduction_rate: 0.1
uc_has_deduction: true
uc_deduction_combination: ADVANCE_ONLY
gov.dwp.universal_credit.standard_allowance.amount.SINGLE_OLD: 440
output:
Expand All @@ -103,6 +106,7 @@
would_claim_uc: true
universal_credit_pre_benefit_cap: 6_000
uc_latent_deduction_rate: 0.1
uc_has_deduction: true
uc_deduction_combination: ADVANCE_ONLY
uc_advance_repayment_amount: 0
output:
Expand All @@ -124,6 +128,7 @@
universal_credit_pre_benefit_cap: 6_000
uc_benefit_cap_reduction: 0
uc_latent_deduction_rate: 0.1
uc_has_deduction: true
uc_deduction_combination: ADVANCE_ONLY
uc_advance_repayment_amount: 0
missing:
Expand All @@ -132,6 +137,7 @@
universal_credit_pre_benefit_cap: 6_000
uc_benefit_cap_reduction: 0
uc_latent_deduction_rate: 0.1
uc_has_deduction: true
uc_deduction_combination: ADVANCE_ONLY
households:
zero_household:
Expand Down
2 changes: 2 additions & 0 deletions policyengine_uk/tests/test_uc_advance_repayment_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ def situation(amounts, year=YEAR, age=30, latent=0.1, combination="ADVANCE_ONLY"
"uc_benefit_cap_reduction": {year: 0},
"uc_latent_deduction_rate": {year: latent},
"uc_deduction_combination": {year: combination},
# The latent rate counts only for a unit with the flag (#2739).
"uc_has_deduction": {year: True},
}
if amount is not None:
unit["uc_advance_repayment_amount"] = {year: amount}
Expand Down
Loading
Loading