diff --git a/changelog.d/uc-hb-split-shared-cap-lha-rules.added.md b/changelog.d/uc-hb-split-shared-cap-lha-rules.added.md new file mode 100644 index 000000000..d6da38960 --- /dev/null +++ b/changelog.d/uc-hb-split-shared-cap-lha-rules.added.md @@ -0,0 +1 @@ +Per-scheme benefit cap variables (`uc_benefit_cap`, `is_uc_benefit_cap_single_claimant_rate`, `is_uc_benefit_cap_exempt`, `is_uc_benefit_cap_exempt_earnings`, `is_uc_benefit_cap_exempt_specified_benefit`, `is_uc_benefit_cap_exempt_qualifying_age`, `uc_benefit_cap_reduction`; `housing_benefit_benefit_cap`, `is_housing_benefit_benefit_cap_single_claimant_rate`, `is_housing_benefit_benefit_cap_exempt`, `is_housing_benefit_benefit_cap_exempt_working_tax_credit`, `is_housing_benefit_benefit_cap_exempt_specified_benefit`, `housing_benefit_benefit_cap_reduction`; `benefit_cap_welfare_benefits`), with the parameter `gov.dwp.housing_benefit.minimum_weekly_amount`. diff --git a/changelog.d/uc-hb-split-shared-cap-lha-rules.fixed.md b/changelog.d/uc-hb-split-shared-cap-lha-rules.fixed.md new file mode 100644 index 000000000..8a007d56f --- /dev/null +++ b/changelog.d/uc-hb-split-shared-cap-lha-rules.fixed.md @@ -0,0 +1 @@ +Universal Credit and Housing Benefit each apply their own benefit cap. The UC cap (UC Regs 2013 regs 79-83) treats a member of a couple claiming as a single person (reg 3(3)) as a single claimant, lifts the cap for earnings and the LCWRA and carer elements but not for working tax credit, lifts it on age only where the single claimant or both joint claimants have reached the qualifying age for State Pension Credit (SI 2014/1230 reg 60C), and takes the childcare costs element off the reduction (reg 81). The HB cap (HB Regs 2006 Part 8A) treats that couple as a couple, lifts the cap for working tax credit and the benefits in reg 75F (the partner's included) but not for earnings or UC elements, does not reach pension-age claims (reg 5), and leaves at least 50 pence a week of HB (reg 75D(2)). A family paid both (Housing Benefit for specified or temporary accommodation alongside UC, which the model gives only when both are supplied) is capped once, through UC: UC counts the Housing Benefit as nil (reg 80(2A)) and the HB cap does not apply to a claimant receiving UC (reg 75F(1)(g)). The UC shared accommodation test (`is_lha_shared_accommodation_rate_specified_renter`, UC Sch 4 paras 27-29) applies reg 3(3) whatever legacy benefits the family reports, and armed forces independence payment now excepts a renter under 35 from it (UC reg 2). Armed forces independence payment lifts the UC cap only when a claimant receives it (UC reg 83(1)(c) with reg 2), no longer when a young person does, and lifts the HB cap when the claimant, the partner or a young person they are responsible for receives it (HB reg 75F(1)(ea)). The shared-rules stopgap for reg 3(3) single claims (`stays_on_legacy_benefits` and the two helpers built on it) is gone. diff --git a/changelog.d/uc-hb-split-shared-cap-lha-rules.removed.md b/changelog.d/uc-hb-split-shared-cap-lha-rules.removed.md new file mode 100644 index 000000000..b935fe863 --- /dev/null +++ b/changelog.d/uc-hb-split-shared-cap-lha-rules.removed.md @@ -0,0 +1 @@ +The shared benefit cap variables `benefit_cap`, `is_benefit_cap_exempt`, `is_benefit_cap_exempt_earnings`, `is_benefit_cap_exempt_health_disability`, `is_benefit_cap_exempt_other` and `is_benefit_cap_single_claimant_rate`, replaced by each scheme's own. `benefit_cap_reduction` remains as the sum of the Universal Credit and Housing Benefit reductions. diff --git a/docs/book/programs/gov/dwp/housing-benefit.ipynb b/docs/book/programs/gov/dwp/housing-benefit.ipynb index 7328c1202..277482eaf 100644 --- a/docs/book/programs/gov/dwp/housing-benefit.ipynb +++ b/docs/book/programs/gov/dwp/housing-benefit.ipynb @@ -40,11 +40,11 @@ "\n", "The award is computed in four stages:\n", "\n", - "1. **Eligible rent**: the rent the benunit is liable for (`benunit_rent`). Where the Local Housing Allowance applies (`LHA_eligible`: renting, and no member in social housing), the eligible rent is the maximum rent (LHA), which is the LHA rate or, where the rent is lower, the rent (`LHA_cap`; SI 2006/213 regs [12D(2)(a)](https://www.legislation.gov.uk/uksi/2006/213/regulation/12D) and [13D(5)](https://www.legislation.gov.uk/uksi/2006/213/regulation/13D)).\n", + "1. **Eligible rent**: the rent the benunit is liable for (`benunit_rent`). Where the Local Housing Allowance applies (`LHA_eligible`: renting, and no member in social housing), the eligible rent is the maximum rent (LHA), which is the LHA rate for the Housing Benefit category of dwelling (`housing_benefit_LHA_category`: the shared accommodation rate for a young individual with no non-dependant, or a claimant entitled to one bedroom who lacks exclusive use of self-contained accommodation, unless the severe disability premium applies, reg 13D(2)) or, where the rent is lower, the rent (`LHA_cap`; SI 2006/213 regs [12D(2)(a)](https://www.legislation.gov.uk/uksi/2006/213/regulation/12D) and [13D(5)](https://www.legislation.gov.uk/uksi/2006/213/regulation/13D)).\n", "2. **Applicable amount** (`housing_benefit_applicable_amount`): a personal allowance derived from the benunit's composition (single, couple, lone parent) and age, plus any disability/carer premiums. This is the \"minimum income\" the household needs before HB tapers begin.\n", "3. **Means test** (`housing_benefit_entitlement`): the appropriate maximum Housing Benefit is the eligible rent less `housing_benefit_non_dep_deductions` for any non-dependant adults living in the household ([reg. 70](https://www.legislation.gov.uk/uksi/2006/213/regulation/70)). The published `withdrawal_rate` (currently 0.65) of the benunit's `housing_benefit_applicable_income` in excess of the applicable amount is deducted from that maximum ([SSCBA 1992 s. 130(3)(b)](https://www.legislation.gov.uk/ukpga/1992/4/section/130), [reg. 71](https://www.legislation.gov.uk/uksi/2006/213/regulation/71)). Because the LHA caps the eligible rent, the taper comes off the LHA: a private tenant with rent above the LHA and income above the applicable amount receives the LHA less the taper, not the lower of the LHA and the rent less the taper. The pension-age regulations (SI 2006/214) have the same rules at regs 12D, 13D, 50 and 51, and the Northern Ireland regulations at SR 2006/405 regs 13C, 14D, 68 and 69 and SR 2006/406 regs 13C, 14D, 48 and 49.\n", " If the claimant or partner is on Universal Credit, Income Support, income-based Jobseeker's Allowance or income-related Employment and Support Allowance (`housing_benefit_on_passporting_benefit`), the whole of the family's earnings, other income and capital is disregarded ([SI 2006/213 Sch. 4 para. 12](https://www.legislation.gov.uk/uksi/2006/213/schedule/4/paragraph/12), [Sch. 5 para. 4](https://www.legislation.gov.uk/uksi/2006/213/schedule/5/paragraph/4) and [Sch. 6 para. 5](https://www.legislation.gov.uk/uksi/2006/213/schedule/6/paragraph/5); Northern Ireland: [SR 2006/405 Sch. 5 para. 12](https://www.legislation.gov.uk/nisr/2006/405/schedule/5/paragraph/12), [Sch. 6 para. 4](https://www.legislation.gov.uk/nisr/2006/405/schedule/6/paragraph/4) and [Sch. 7 para. 5](https://www.legislation.gov.uk/nisr/2006/405/schedule/7/paragraph/5)). `housing_benefit_applicable_income` and `housing_benefit_assessable_capital` are then zero, so the award is that maximum. This applies at any age: a mixed-age couple whose younger member is on one of these benefits is assessed under the working-age regulations ([SI 2006/213 reg. 5(1)(b)](https://www.legislation.gov.uk/uksi/2006/213/regulation/5)). Income Support, income-based Jobseeker's Allowance and income-related Employment and Support Allowance count when a positive amount is paid on the claimant's or partner's award (`in_receipt_of_income_support_jsa_ib_or_esa_ir`, which reads `claimant_or_partner_jsa_income` and `claimant_or_partner_esa_income`). Another member's own award, such as a non-dependant's, is payable to them and does not passport the claimant. A person is on Universal Credit on any day they are entitled to it, whether it is in payment or not ([reg. 2(3B)](https://www.legislation.gov.uk/uksi/2006/213/regulation/2)); PolicyEngine reads that as Universal Credit above nil before the benefit cap and deductions (`is_uc_entitled`), for a family that claims it, so the passport adds no circular dependency through the benefit cap. The universal credit limb applies from 28 October 2013 in Great Britain and 8 May 2018 in Northern Ireland (`gov.dwp.housing_benefit.means_test.universal_credit_passport`). It matters only where Housing Benefit is paid alongside Universal Credit, for specified or temporary accommodation, which the eligibility rules above do not yet model.\n", - "4. **Benefit cap** (`housing_benefit`): the post-means-test award is finally reduced by `benefit_cap_reduction` if the household's total benefit income exceeds the cap and no cap exemption applies.\n", + "4. **Benefit cap** (`housing_benefit`): the post-means-test award is finally reduced by `housing_benefit_benefit_cap_reduction`, the excess of the family's welfare benefits over the Housing Benefit cap (`housing_benefit_benefit_cap`, SI 2006/213 reg 75CA), leaving at least 50 pence a week (reg 75D). The cap does not apply where the claimant or couple is entitled to working tax credit (reg 75E), the claimant or partner receives a benefit listed in reg 75F, or the claim is under the pension-age regulations (reg 5). Universal Credit has its own cap.\n", "\n", "The take-up gate (`would_claim_housing_benefit`) is applied inside `housing_benefit_pre_benefit_cap`." ] diff --git a/docs/book/programs/gov/dwp/universal-credit.ipynb b/docs/book/programs/gov/dwp/universal-credit.ipynb index 218613d77..5a6a07caf 100644 --- a/docs/book/programs/gov/dwp/universal-credit.ipynb +++ b/docs/book/programs/gov/dwp/universal-credit.ipynb @@ -44,7 +44,7 @@ { "cell_type": "markdown", "metadata": {}, - "source": "## How PolicyEngine computes Universal Credit\n\nPolicyEngine computes Universal Credit at the benefit-unit level in three stages:\n\n1. **Maximum entitlement** (`uc_maximum_amount`): sum the elements the benunit qualifies for — `uc_standard_allowance`, `uc_child_element`, `uc_disability_elements`, `uc_carer_element`, `uc_housing_costs_element`, and `uc_childcare_element`.\n2. **Means test**: only the claimants' income counts: the single claimant, or the two joint claimants (`is_uc_assessed_claimant`; UC Regulations 2013 reg 22(1)). The earnings and unearned income of a child, a qualifying young person or anyone else in the benefit unit are theirs, not the claimant's, so the Universal Credit means test does not count them. Who the claimants are comes from `is_uc_claimant` (the claimant and partner); a member flagged there is taken as a claimant whatever their age. Each person's earnings (`uc_individual_earned_income`) are net of their own relievable pension contributions and the income tax and National Insurance they pay on that employment or self-employment (UC Regulations 2013 regs 55(5) and 57(2)): `uc_income_tax_on_earnings` treats earnings as the lowest slice of the person's non-savings income, so tax on pensions, property, savings and dividends is never deducted, and one partner's tax never reduces the other's earnings. A claimant in gainful self-employment outside a start-up period who would otherwise be subject to all work-related requirements is subject to the minimum income floor (reg 62): when their earnings after those deductions are below `uc_minimum_income_floor`, they are treated as having that amount instead. The floor does not apply to a claimant in the no work-related requirements, work-focused interview or work preparation group (`uc_work_related_group_apart_from_earnings`; Welfare Reform Act 2012 ss 19 to 21): for example one with limited capability for work-related activity, a carer, a claimant over State Pension Credit qualifying age, or the responsible carer of a child under 3. Only one member of a couple is the responsible carer (`uc_is_responsible_carer`); where the nomination is not supplied the model takes the claimant who works fewer hours (`hours_worked`), then the elder. `hours_worked` defaults to 0, so for a couple entered without hours the default falls to the elder; supply `uc_is_responsible_carer` where the nomination is known. The floor is the minimum wage for their age for their expected hours (`uc_expected_hours`: 35 a week, or for the responsible carer of a child under 13 the hours DWP uses: 30 from February 2025, from April 2017 until then 16 for a child under compulsory school age and 25 for an older child, and 25 for any child under 13 before April 2017; regs 88 and 90(2)), less the income tax and the Class 2 and Class 4 National Insurance a self-employed person would pay on it (reg 62(4)(b) leaves this amount to the Secretary of State; this is the basis of DWP's guidance and figures, and setting the `gov.dwp.universal_credit.means_test.minimum_income_floor.self_employed_national_insurance` parameter to false deducts primary Class 1 instead). A member of a couple is lifted only while the couple's combined earnings are below the couple threshold, and never above it (reg 62(3)). The couple threshold is the sum of both partners' thresholds (reg 90(3)): 16 hours for a partner in the work-focused interview or work preparation group, nothing for a partner with no work-related requirements for a reason other than earnings, and 35 hours at the national living wage (the adult rate before April 2016) for a partner who cannot be a joint claimant (reg 90(3)(b)). The maximum entitlement is then reduced by 55% (the `gov.dwp.universal_credit.means_test.reduction_rate` parameter) of the claimants' combined earnings above the work allowance (`uc_work_allowance`), and by all of the claimants' unearned income (`uc_unearned_income`), pound for pound. Unearned income is the list in `gov.dwp.universal_credit.means_test.income_definitions.unearned`: retirement pension income (State Pension and private pensions, UC Regulations 2013 regs 66(1)(a) and 67), Carer's Allowance, carer support payment (up to the Carer's Allowance rate, from November 2023), contribution-based JSA and ESA, maternity allowance, industrial injuries benefit, and the tariff income deemed from capital (reg 72). Actual savings interest, dividends and ordinary rental income are excluded from UC unearned income at every capital level; capital counts through its tariff income instead. Asset rules apply on top of this — capital above the lower threshold deems a tariff income, and capital above the upper threshold disqualifies the benunit entirely.\n3. **Benefit cap**: the post-means-test award (`universal_credit_pre_benefit_cap`) is finally reduced by `benefit_cap_reduction` to produce `universal_credit`. The cap does not apply where the claimants earn enough (the earnings exception reads the claimants' earnings only, reg 82(1)(a)) or the family gets working tax credit, or where a claimant or, for some benefits, a child or qualifying young person they are responsible for receives a benefit listed in UC Regs 2013 reg 83 (for example ESA with the support component, disability living allowance or personal independence payment). Another member's benefits do not count, and nor do those of a partner who cannot be a joint claimant under reg 3(3) (`is_benefit_cap_exempt`). The capped total counts only the welfare benefits to which the claimant or couple is entitled (WRA 2012 s.96(1); reg 80(1)), and the childcare work condition tests only the claimants (reg 32(1)).\n\nUniversal Credit is paid only to families that claim it (`claims_universal_credit`). There are two routes. The input `would_claim_uc` is drawn stochastically when the dataset is built, so that caseload aggregates match published DWP claimant numbers rather than the full eligible population. The second route covers a family whose legacy benefit has closed: tax credits from 2025-26, Income Support and income-based JSA from 2026-27 (`legacy_benefits_closed`). DWP's migration notice ended all such a family's legacy awards, and it claims Universal Credit if the input `would_claim_uc_at_legacy_closure` is true. The dataset draws that input at DWP's Move to Universal Credit claim rates by legacy benefit combination. Families DWP sent to Pension Credit instead are excluded: those on Pension Credit, and those on Child Tax Credit only that meet the Pension Credit age conditions (SI 2019/167 art. 3A). Pension-age families on Working Tax Credit can claim, because the upper age condition is waived for them (SI 2014/1230 reg 60A). Protected mixed-age couples on it can claim jointly (UC Regulations 2013 reg 3(2)(a)). Such a family leaves the pension-age route only if it claims and has positive Universal Credit entitlement before the benefit cap and deductions. A protected mixed-age couple that does not claim, or claims but has nil entitlement, can reclaim Housing Benefit and keep its saving (SI 2019/37 art. 4(3)(b)(ii), as amended by SI 2024/604 art. 2). For Council Tax Reduction, Scotland treats pension-age tax credit migrants entitled to Universal Credit as working-age applicants. England disregards their reg 60A awards for schemes from April 2025, and Wales does so only for schemes from April 2026 (SI 2025/39; WSI 2026/13). Both inputs default to true. To model a family that does not claim, set both to false. Not modelled: transitional protection, including the transitional capital disregard (SI 2014/1230 regs 48-57); pension-age and protected mixed-age Child Tax Credit recipients whose Working Tax Credit entitlement tapered to nil, whom DWP also moved to Universal Credit (HB circular A9/2024 paras 6 and 10). Reported award amounts and current annual hours/income do not establish pre-migration Working Tax Credit entitlement. This gap requires a methodology decision or historical-entitlement input; no proxy is selected; and the choice between routes, since a pension-age family that claims may get less than it would by declining.\n\nParameters live in `policyengine_uk/parameters/gov/dwp/universal_credit/` and the per-element formulas in `policyengine_uk/variables/gov/dwp/universal_credit/`." + "source": "## How PolicyEngine computes Universal Credit\n\nPolicyEngine computes Universal Credit at the benefit-unit level in three stages:\n\n1. **Maximum entitlement** (`uc_maximum_amount`): sum the elements the benunit qualifies for — `uc_standard_allowance`, `uc_child_element`, `uc_disability_elements`, `uc_carer_element`, `uc_housing_costs_element`, and `uc_childcare_element`.\n2. **Means test**: only the claimants' income counts: the single claimant, or the two joint claimants (`is_uc_assessed_claimant`; UC Regulations 2013 reg 22(1)). The earnings and unearned income of a child, a qualifying young person or anyone else in the benefit unit are theirs, not the claimant's, so the Universal Credit means test does not count them. Who the claimants are comes from `is_uc_claimant` (the claimant and partner); a member flagged there is taken as a claimant whatever their age. Each person's earnings (`uc_individual_earned_income`) are net of their own relievable pension contributions and the income tax and National Insurance they pay on that employment or self-employment (UC Regulations 2013 regs 55(5) and 57(2)): `uc_income_tax_on_earnings` treats earnings as the lowest slice of the person's non-savings income, so tax on pensions, property, savings and dividends is never deducted, and one partner's tax never reduces the other's earnings. A claimant in gainful self-employment outside a start-up period who would otherwise be subject to all work-related requirements is subject to the minimum income floor (reg 62): when their earnings after those deductions are below `uc_minimum_income_floor`, they are treated as having that amount instead. The floor does not apply to a claimant in the no work-related requirements, work-focused interview or work preparation group (`uc_work_related_group_apart_from_earnings`; Welfare Reform Act 2012 ss 19 to 21): for example one with limited capability for work-related activity, a carer, a claimant over State Pension Credit qualifying age, or the responsible carer of a child under 3. Only one member of a couple is the responsible carer (`uc_is_responsible_carer`); where the nomination is not supplied the model takes the claimant who works fewer hours (`hours_worked`), then the elder. `hours_worked` defaults to 0, so for a couple entered without hours the default falls to the elder; supply `uc_is_responsible_carer` where the nomination is known. The floor is the minimum wage for their age for their expected hours (`uc_expected_hours`: 35 a week, or for the responsible carer of a child under 13 the hours DWP uses: 30 from February 2025, from April 2017 until then 16 for a child under compulsory school age and 25 for an older child, and 25 for any child under 13 before April 2017; regs 88 and 90(2)), less the income tax and the Class 2 and Class 4 National Insurance a self-employed person would pay on it (reg 62(4)(b) leaves this amount to the Secretary of State; this is the basis of DWP's guidance and figures, and setting the `gov.dwp.universal_credit.means_test.minimum_income_floor.self_employed_national_insurance` parameter to false deducts primary Class 1 instead). A member of a couple is lifted only while the couple's combined earnings are below the couple threshold, and never above it (reg 62(3)). The couple threshold is the sum of both partners' thresholds (reg 90(3)): 16 hours for a partner in the work-focused interview or work preparation group, nothing for a partner with no work-related requirements for a reason other than earnings, and 35 hours at the national living wage (the adult rate before April 2016) for a partner who cannot be a joint claimant (reg 90(3)(b)). The maximum entitlement is then reduced by 55% (the `gov.dwp.universal_credit.means_test.reduction_rate` parameter) of the claimants' combined earnings above the work allowance (`uc_work_allowance`), and by all of the claimants' unearned income (`uc_unearned_income`), pound for pound. Unearned income is the list in `gov.dwp.universal_credit.means_test.income_definitions.unearned`: retirement pension income (State Pension and private pensions, UC Regulations 2013 regs 66(1)(a) and 67), Carer's Allowance, carer support payment (up to the Carer's Allowance rate, from November 2023), contribution-based JSA and ESA, maternity allowance, industrial injuries benefit, and the tariff income deemed from capital (reg 72). Actual savings interest, dividends and ordinary rental income are excluded from UC unearned income at every capital level; capital counts through its tariff income instead. Asset rules apply on top of this — capital above the lower threshold deems a tariff income, and capital above the upper threshold disqualifies the benunit entirely.\n3. **Benefit cap**: the post-means-test award (`universal_credit_pre_benefit_cap`) is finally reduced by `uc_benefit_cap_reduction` to produce `universal_credit`: the excess of the family's welfare benefits over the Universal Credit cap (`uc_benefit_cap`, UC Regulations 2013 reg 80A), less the childcare costs element (reg 81). The cap does not apply where the claimants' earnings reach the reg 82 threshold (the earnings exception reads the claimants' earnings only, reg 82(1)(a)), where the award includes the LCWRA or carer element or a claimant or, for some benefits, a child or qualifying young person they are responsible for receives a benefit listed in reg 83 (for example ESA with the support component, disability living allowance or personal independence payment), or where the single claimant or both joint claimants have reached the qualifying age for State Pension Credit (SI 2014/1230 reg 60C) (`is_uc_benefit_cap_exempt`). Another member's benefits do not count, and nor do those of a partner who cannot be a joint claimant under reg 3(3). A member of a couple claiming as a single person (reg 3(3)) has the single-claimant limit. The capped total counts only the welfare benefits to which the claimant or couple is entitled (WRA 2012 s.96(1); reg 80(1)), and the childcare work condition tests only the claimants (reg 32(1)). Housing Benefit has its own cap.\n\nUniversal Credit is paid only to families that claim it (`claims_universal_credit`). There are two routes. The input `would_claim_uc` is drawn stochastically when the dataset is built, so that caseload aggregates match published DWP claimant numbers rather than the full eligible population. The second route covers a family whose legacy benefit has closed: tax credits from 2025-26, Income Support and income-based JSA from 2026-27 (`legacy_benefits_closed`). DWP's migration notice ended all such a family's legacy awards, and it claims Universal Credit if the input `would_claim_uc_at_legacy_closure` is true. The dataset draws that input at DWP's Move to Universal Credit claim rates by legacy benefit combination. Families DWP sent to Pension Credit instead are excluded: those on Pension Credit, and those on Child Tax Credit only that meet the Pension Credit age conditions (SI 2019/167 art. 3A). Pension-age families on Working Tax Credit can claim, because the upper age condition is waived for them (SI 2014/1230 reg 60A). Protected mixed-age couples on it can claim jointly (UC Regulations 2013 reg 3(2)(a)). Such a family leaves the pension-age route only if it claims and has positive Universal Credit entitlement before the benefit cap and deductions. A protected mixed-age couple that does not claim, or claims but has nil entitlement, can reclaim Housing Benefit and keep its saving (SI 2019/37 art. 4(3)(b)(ii), as amended by SI 2024/604 art. 2). For Council Tax Reduction, Scotland treats pension-age tax credit migrants entitled to Universal Credit as working-age applicants. England disregards their reg 60A awards for schemes from April 2025, and Wales does so only for schemes from April 2026 (SI 2025/39; WSI 2026/13). Both inputs default to true. To model a family that does not claim, set both to false. Not modelled: transitional protection, including the transitional capital disregard (SI 2014/1230 regs 48-57); pension-age and protected mixed-age Child Tax Credit recipients whose Working Tax Credit entitlement tapered to nil, whom DWP also moved to Universal Credit (HB circular A9/2024 paras 6 and 10). Reported award amounts and current annual hours/income do not establish pre-migration Working Tax Credit entitlement. This gap requires a methodology decision or historical-entitlement input; no proxy is selected; and the choice between routes, since a pension-age family that claims may get less than it would by declining.\n\nParameters live in `policyengine_uk/parameters/gov/dwp/universal_credit/` and the per-element formulas in `policyengine_uk/variables/gov/dwp/universal_credit/`." }, { "cell_type": "code", diff --git a/docs/book/usage/scenarios.md b/docs/book/usage/scenarios.md index 5050ae1b6..273668fd2 100644 --- a/docs/book/usage/scenarios.md +++ b/docs/book/usage/scenarios.md @@ -272,8 +272,8 @@ baseline_sim = Simulation(situation=benefit_cap_family) reformed_sim = Simulation(situation=benefit_cap_family, scenario=reindex_benefit_cap) # Compare the benefit cap levels -baseline_cap = baseline_sim.calculate("benefit_cap", 2026).mean() -reformed_cap = reformed_sim.calculate("benefit_cap", 2026).mean() +baseline_cap = baseline_sim.calculate("uc_benefit_cap", 2026).mean() +reformed_cap = reformed_sim.calculate("uc_benefit_cap", 2026).mean() print( f"Benefit cap - frozen: £{baseline_cap:.0f}/year, indexed: £{reformed_cap:.0f}/year" diff --git a/policyengine_uk/parameters/gov/dwp/LHA/shared_accommodation_age_threshold.yaml b/policyengine_uk/parameters/gov/dwp/LHA/shared_accommodation_age_threshold.yaml index e47ddeb80..4e49290cc 100644 --- a/policyengine_uk/parameters/gov/dwp/LHA/shared_accommodation_age_threshold.yaml +++ b/policyengine_uk/parameters/gov/dwp/LHA/shared_accommodation_age_threshold.yaml @@ -6,7 +6,9 @@ metadata: unit: year label: Lha shared accommodation age threshold reference: - - title: The Housing Benefit Regulations 2006 - Schedule 4 - Paragraph 28 + - title: Universal Credit Regulations 2013 Sch 4 para 28(2)(a) href: https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/28 + - title: Housing Benefit Regulations 2006 reg 2(1) ("young individual") + href: https://www.legislation.gov.uk/uksi/2006/213/regulation/2 - title: LHA shared accommodation rate guidance - href: https://www.gov.uk/government/publications/local-housing-allowance-rates-applicable-from-april-2013 \ No newline at end of file + href: https://www.gov.uk/government/publications/local-housing-allowance-rates-applicable-from-april-2013 diff --git a/policyengine_uk/parameters/gov/dwp/LHA/shared_accommodation_exception_benefits.yaml b/policyengine_uk/parameters/gov/dwp/LHA/shared_accommodation_exception_benefits.yaml index 26f65c249..9f9765f98 100644 --- a/policyengine_uk/parameters/gov/dwp/LHA/shared_accommodation_exception_benefits.yaml +++ b/policyengine_uk/parameters/gov/dwp/LHA/shared_accommodation_exception_benefits.yaml @@ -1,12 +1,15 @@ -description: Disability benefits whose recipients under 35 are excepted from the shared accommodation rate. +description: Disability benefits whose recipients under 35 are excepted from the Universal Credit shared accommodation rate. values: 2013-04-29: - attendance_allowance - dla_sc_middle_plus - pip_dl + - armed_forces_independence_payment metadata: unit: list - label: shared accommodation rate disability exceptions + label: Universal Credit shared accommodation rate disability exceptions reference: - title: Universal Credit Regulations 2013 Sch 4 para 29(5) href: https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/29 + - title: Universal Credit Regulations 2013 reg 2 ("attendance allowance" includes armed forces independence payment) + href: https://www.legislation.gov.uk/uksi/2013/376/regulation/2 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/minimum_weekly_amount.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/minimum_weekly_amount.yaml new file mode 100644 index 000000000..24e98a414 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/minimum_weekly_amount.yaml @@ -0,0 +1,12 @@ +description: Housing Benefit is not payable where the weekly amount would be less than this; where the benefit cap would reduce Housing Benefit below it, the reduction leaves this amount. +values: + 2006-03-06: 0.5 +metadata: + unit: currency-GBP + period: week + label: Housing Benefit minimum weekly amount + reference: + - title: Housing Benefit Regulations 2006 reg. 75 (minimum housing benefit) + href: https://www.legislation.gov.uk/uksi/2006/213/regulation/75 + - title: Housing Benefit Regulations 2006 reg. 75D(2) (reduction of housing benefit) + href: https://www.legislation.gov.uk/uksi/2006/213/regulation/75D diff --git a/policyengine_uk/programs.yaml b/policyengine_uk/programs.yaml index 89e66fdfa..2fa867458 100644 --- a/policyengine_uk/programs.yaml +++ b/policyengine_uk/programs.yaml @@ -318,9 +318,10 @@ programs: agency: DWP status: complete coverage: UK - variable: benefit_cap + variable: benefit_cap_reduction parameter_prefix: gov.dwp verified_start_year: 2022 + notes: Universal Credit (uc_benefit_cap) and Housing Benefit (housing_benefit_benefit_cap) each apply their own cap; benefit_cap_reduction is the sum of their reductions - id: tax_credits name: Tax credits diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_claimant_composition.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_claimant_composition.yaml index 9f2078bd7..79162f094 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_claimant_composition.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_claimant_composition.yaml @@ -1,7 +1,11 @@ -# UC Regulations 2013, Schedule 4 paragraphs 9 and 27-28: +# UC Regulations 2013, Schedule 4 paragraphs 9 and 27-29 (LHA_category): # https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/28 +# HB Regulations 2006 reg 13D(2)(a)(i) and reg 2(1) "young individual" +# (housing_benefit_LHA_category): +# https://www.legislation.gov.uk/uksi/2006/213/regulation/13D # The existing bedroom calculation is unchanged: B = 1 bedroom, C = 2; # A is the shared accommodation rate, overriding the bedroom calculation. +# Where the schemes agree, each case asserts both categories. - name: Single 17-year-old renter is not their own dependent child period: 2025 input: @@ -13,6 +17,7 @@ is_lha_shared_accommodation_rate_specified_renter: true # Single; 17 < 35; no dependant or non-dependant: A (previously B). LHA_category: A + housing_benefit_LHA_category: A - name: Single 30-year-old renter remains subject to the shared rate period: 2025 @@ -23,6 +28,7 @@ is_lha_shared_accommodation_rate_specified_renter: true # Single; 30 < 35; no other occupants: A. LHA_category: A + housing_benefit_LHA_category: A - name: Single renter immediately below the shared rate age threshold period: 2025 @@ -32,6 +38,7 @@ is_lha_shared_accommodation_rate_specified_renter: true # 34 < 35: A. LHA_category: A + housing_benefit_LHA_category: A - name: Single renter at the shared rate age threshold period: 2025 @@ -41,6 +48,7 @@ is_lha_shared_accommodation_rate_specified_renter: false # 35 is not below 35; 1 bedroom: B. LHA_category: B + housing_benefit_LHA_category: B - name: A 17-year-old apprentice in the renter's benefit unit is a non-dependant period: 2025 @@ -65,6 +73,9 @@ is_lha_shared_accommodation_rate_specified_renter: false # No UC QYP, but a non-dependant blocks A. Existing bedroom formula = 1: B. LHA_category: B + # HB reg 13D(3)(a)-(b): a bedroom for the renter and one for the + # 17-year-old, who is not a child (reg 2(1): under 16): 2 bedrooms, C. + housing_benefit_LHA_category: C - name: Lone parent with an 18-year-old qualifying young person period: 2025 @@ -89,8 +100,11 @@ is_lha_shared_accommodation_rate_specified_renter: false # Responsibility for a UC QYP blocks A. Existing bedroom formula = 1: B. LHA_category: B + # HB reg 13D(3)(a)-(b): a bedroom for the renter and one for the + # 18-year-old, who is not a child (reg 2(1): under 16): 2 bedrooms, C. + housing_benefit_LHA_category: C -- name: Renter aged 30 whose partner is 17 claims as a single person and is a specified renter +- name: Renter aged 30 whose partner is 17 claims UC as a single person and is a specified renter; for HB they are a couple period: 2025 input: people: @@ -118,6 +132,11 @@ uc_member_of_couple_claims_as_single_person: true is_lha_shared_accommodation_rate_specified_renter: true LHA_category: A + # HB has no single claim by a member of a couple: a claimant with a + # partner is not a "single claimant", so not a young individual (HB reg + # 2(1)); the couple's one bedroom (reg 13D(3)(a)) gives B. + is_housing_benefit_young_individual: false + housing_benefit_LHA_category: B - name: Couple aged 30 and 17 where the partner is within reg. 8 is not a specified renter period: 2025 @@ -143,6 +162,7 @@ is_lha_shared_accommodation_rate_specified_renter: false # Joint renters have one bedroom (Sch 4 para 10(1)(a)): B. LHA_category: B + housing_benefit_LHA_category: B - name: A 17-year-old in another family is a non-dependant of the household head only period: 2025 @@ -246,6 +266,7 @@ # Single, 30 < 35, nobody else in this household: A. # Other renter 40 >= 35; 1 bedroom: B. LHA_category: [A, B] + housing_benefit_LHA_category: [A, B] - name: Actual shared accommodation gives the shared rate for Housing Benefit only period: 2025 @@ -271,6 +292,7 @@ output: is_lha_shared_accommodation_rate_specified_renter: false LHA_category: B + housing_benefit_LHA_category: B - name: A disabled 16-year-old renter is excepted from the shared accommodation rate # UC Sch 4 para 29(5)(a): the DLA care component at the middle or highest @@ -282,6 +304,7 @@ output: is_lha_shared_accommodation_rate_specified_renter: false LHA_category: B + housing_benefit_LHA_category: B - name: A 16-year-old renter without a disability benefit gets the shared rate period: 2025 @@ -290,6 +313,7 @@ output: is_lha_shared_accommodation_rate_specified_renter: true LHA_category: A + housing_benefit_LHA_category: A - name: A renter under 35 on attendance allowance is excepted from the shared rate period: 2025 @@ -299,3 +323,4 @@ output: is_lha_shared_accommodation_rate_specified_renter: false LHA_category: B + housing_benefit_LHA_category: B diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit.yaml index 9b0473a2e..2aadfd747 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit.yaml @@ -3,7 +3,7 @@ absolute_error_margin: 100 input: housing_benefit_pre_benefit_cap: 1_000 - benefit_cap_reduction: 400 + housing_benefit_benefit_cap_reduction: 400 would_claim_housing_benefit: true output: housing_benefit: 600 @@ -13,7 +13,7 @@ absolute_error_margin: 100 input: housing_benefit_pre_benefit_cap: 1_000 - benefit_cap_reduction: 3_000 + housing_benefit_benefit_cap_reduction: 3_000 would_claim_housing_benefit: true output: housing_benefit: 0 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_lha_category.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_lha_category.yaml new file mode 100644 index 000000000..1277152cf --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_lha_category.yaml @@ -0,0 +1,198 @@ +# Housing Benefit's own LHA category (HB Regs 2006 reg 13D(2)), beside +# Universal Credit's (UC Regs 2013 Sch 4 paras 27-29, LHA_category). +# +# HB reg 13D(2)(a)(i): shared accommodation where "the claimant is a young +# individual who has no non-dependant residing with him and to whom paragraph +# 14 of Schedule 3 (severe disability premium) does not apply". Reg 2(1): +# "young individual" means a single claimant under 35 (with exceptions); +# "single claimant" means a claimant who neither has a partner nor is a lone +# parent. Sch 3 para 14(2)(a): a single claimant is severely disabled if (i) +# in receipt of attendance allowance, DLA care at the highest or middle rate, +# the PIP daily living component or armed forces independence payment +# (among others), (ii) no non-dependant aged 18 or over resides with them, +# and (iii) no one receives carer's allowance for caring for them (not +# observed). Para 14(2)(b): for a couple, both must qualify; para 14(4): a +# non-dependant who receives a qualifying benefit is disregarded. +# UC Sch 4 para 29(5)(b): attendance allowance, which UC reg 2 defines to +# include armed forces independence payment. + +- name: A renter under 35 receiving armed forces independence payment is excepted from both schemes' shared rate + period: 2025 + input: + age: 30 + armed_forces_independence_payment: 6_000 + output: + # UC Sch 4 para 29(5)(b) with reg 2: one bedroom, B. + is_lha_shared_accommodation_rate_specified_renter: false + LHA_category: B + # HB: a young individual to whom Sch 3 para 14 applies, so not 13D(2)(a)(i). + is_housing_benefit_young_individual: true + housing_benefit_severe_disability_premium_applies: true + housing_benefit_LHA_category: B + +- name: A single claimant under 35 with no qualifying benefit gets the HB shared rate + period: 2025 + input: + age: 30 + output: + is_housing_benefit_young_individual: true + housing_benefit_severe_disability_premium_applies: false + housing_benefit_LHA_category: A + +- name: A single claimant aged 35 is not a young individual + period: 2025 + input: + age: 35 + output: + is_housing_benefit_young_individual: false + housing_benefit_LHA_category: B + +- name: Sch 3 para 14(2)(b) - a couple meets the severe disability premium condition only if both qualify + period: 2025 + input: + people: + claimant: + age: 30 + is_claimant_or_partner: true + attendance_allowance: 4_000 + partner: + age: 30 + is_claimant_or_partner: true + benunits: + benunit: + members: [claimant, partner] + households: + household: + members: [claimant, partner] + output: + housing_benefit_severe_disability_premium_applies: false + # A couple is never a young individual: one bedroom, B. + is_housing_benefit_young_individual: false + housing_benefit_LHA_category: B + +- name: Sch 3 para 14(2)(b) - both members of a couple qualifying meets the condition + period: 2025 + input: + people: + claimant: + age: 30 + is_claimant_or_partner: true + attendance_allowance: 4_000 + partner: + age: 30 + is_claimant_or_partner: true + pip_dl: 3_000 + benunits: + benunit: + members: [claimant, partner] + households: + household: + members: [claimant, partner] + output: + housing_benefit_severe_disability_premium_applies: true + +- name: Sch 3 para 14(2)(a)(ii) - an adult non-dependant in the family defeats the severe disability premium + period: 2025 + input: + people: + claimant: + age: 30 + is_claimant_or_partner: true + attendance_allowance: 4_000 + adult: + age: 25 + is_claimant_or_partner: false + benunits: + benunit: + members: [claimant, adult] + households: + household: + members: [claimant, adult] + output: + housing_benefit_severe_disability_premium_applies: false + +- name: Sch 3 para 14(4)(a) - a non-dependant receiving a qualifying benefit is disregarded + period: 2025 + input: + people: + claimant: + age: 30 + is_claimant_or_partner: true + attendance_allowance: 4_000 + adult: + age: 25 + is_claimant_or_partner: false + pip_dl: 3_000 + benunits: + benunit: + members: [claimant, adult] + households: + household: + members: [claimant, adult] + output: + housing_benefit_severe_disability_premium_applies: true + +- name: An adult in another family who is the household head's non-dependant defeats the severe disability premium + period: 2025 + input: + people: + claimant: + age: 30 + attendance_allowance: 4_000 + # The claimant's family is liable for the rent. + is_household_head: true + non_dependant: + age: 40 + benunits: + claimant_unit: + members: [claimant] + non_dependant_unit: + members: [non_dependant] + households: + household: + members: [claimant, non_dependant] + output: + # HB reg 3: the other adult normally resides with the claimant and is not + # liable to pay for their occupation, so is a non-dependant; aged 18 or + # over with no qualifying benefit, they defeat Sch 3 para 14(2)(a)(ii). + is_non_dependant_of_household_head: [false, true] + housing_benefit_severe_disability_premium_applies: [false, false] + +- name: An HB young person who is not a UC qualifying young person blocks both shared rates + # The 19-year-old started non-advanced education at 17 and is past UC's + # terminal date, so is a Child Benefit (HB reg 19) young person but not a + # UC qualifying young person (UC reg 5). For UC the parent is responsible + # for no child or qualifying young person, but the 19-year-old is a + # non-dependant (Sch 4 para 9), so the parent is not a specified renter + # (para 28(4)). For HB the parent is a lone parent, not a single claimant, + # so not a young individual (reg 2(1)). Each scheme gives the parent a + # bedroom and the 19-year-old, "a non-dependant who is not a child" (UC + # Sch 4 para 10(1)) or "a person who is not a child" (HB reg 13D(3)(b)), + # another: two bedrooms, C. The model's bedroom count does not yet give + # benefit-unit members over 16 their own room (PE-UK #2010), so the test + # supplies it. + period: 2025 + input: + people: + parent: + age: 30 + is_parent: true + student: + age: 19 + current_education: UPPER_SECONDARY + age_started_or_accepted_current_education_or_training: 17 + is_before_universal_credit_qualifying_young_person_terminal_date: false + benunits: + benunit: + members: [parent, student] + LHA_allowed_bedrooms: 2 + households: + household: + members: [parent, student] + output: + is_responsible_for_child_or_qualifying_young_person_for_universal_credit: false + is_responsible_for_child_or_young_person_for_legacy_benefits: true + is_lha_shared_accommodation_rate_specified_renter: false + is_housing_benefit_young_individual: false + LHA_category: C + housing_benefit_LHA_category: C diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/passports.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/passports.yaml index 47bd05041..cb78e220c 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/passports.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/legacy_award_readers/passports.yaml @@ -338,7 +338,8 @@ targeted_childcare_entitlement_eligible: true # Benefit cap: no cap where "the claimant or the claimant's partner" receives -# ESA with the support component (HB Regs 2006 reg 75F(1)(a)). +# ESA with the support component (HB Regs 2006 reg 75F(1)(a)); Universal +# Credit's exception names "a claimant" (UC Regs 2013 reg 83(1)(a)). - name: Another member's income-related ESA does not exempt the family from the benefit cap period: 2025 @@ -361,7 +362,8 @@ household: members: [claimant, other] output: - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: The claimant's own income-related ESA with the support component exempts the family from the benefit cap period: 2025 @@ -384,7 +386,8 @@ household: members: [claimant, other] output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true # Maintenance loans: the "students entitled to benefits" schedule (SI 2011/1986 # reg 71(1)(h)(iii)) follows the special support grant test in reg 61(2)(b), diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claimant_child_element.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claimant_child_element.yaml index 277809882..6ed4fd7ae 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claimant_child_element.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claimant_child_element.yaml @@ -16,7 +16,7 @@ members: [person1] uc_reported_capital: 0 uc_latent_deduction_rate: 0 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: [person1] @@ -50,7 +50,7 @@ members: [person1, person2] uc_reported_capital: 0 uc_latent_deduction_rate: 0 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: [person1, person2] @@ -86,7 +86,7 @@ members: [person1, person2, person3, person4] uc_reported_capital: 0 uc_latent_deduction_rate: 0 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: [person1, person2, person3, person4] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claimant_relationships.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claimant_relationships.yaml index e9eee500e..bc017468a 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claimant_relationships.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claimant_relationships.yaml @@ -24,7 +24,7 @@ members: [person1, person2] uc_reported_capital: 0 uc_latent_deduction_rate: 0 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: [person1, person2] @@ -56,7 +56,7 @@ members: [person1, person2] uc_reported_capital: 0 uc_latent_deduction_rate: 0 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: [person1, person2] @@ -91,7 +91,7 @@ is_married: false uc_reported_capital: 0 uc_latent_deduction_rate: 0 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: [person1, person2, person3] @@ -116,7 +116,7 @@ is_married: false uc_reported_capital: 0 uc_latent_deduction_rate: 0 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: [person1, person2] @@ -173,7 +173,7 @@ members: [person1, person2] uc_reported_capital: 0 uc_latent_deduction_rate: 0 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: [person1, person2] @@ -203,7 +203,7 @@ members: [person1, person2] uc_reported_capital: 0 uc_latent_deduction_rate: 0 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: [person1, person2] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claims_as_single_person.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claims_as_single_person.yaml index 4176316e7..66b8c4e5e 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claims_as_single_person.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/claims_as_single_person.yaml @@ -586,14 +586,18 @@ benunits: benunit: members: [claimant, partner, child] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: household: members: [claimant, partner, child] region: NORTH_EAST output: - is_benefit_cap_single_claimant_rate: false - benefit_cap: 22_020 + is_uc_benefit_cap_single_claimant_rate: false + uc_benefit_cap: 22_020 + # HB: a couple with a child, so not a single claimant either. + is_housing_benefit_benefit_cap_single_claimant_rate: false + housing_benefit_benefit_cap: 22_020 - name: Reg. 80A(2)(c) - a single claimant with no child has the single cap period: 2026 @@ -609,14 +613,19 @@ benunits: benunit: members: [claimant, partner] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: household: members: [claimant, partner] region: NORTH_EAST output: - is_benefit_cap_single_claimant_rate: true - benefit_cap: 14_753 + is_uc_benefit_cap_single_claimant_rate: true + uc_benefit_cap: 14_753 + # HB has no single claim by a member of a couple: the claimant has a + # partner, so is not a single claimant (HB reg 2(1)); reg. 75CA(2)(d). + is_housing_benefit_benefit_cap_single_claimant_rate: false + housing_benefit_benefit_cap: 22_020 - name: Reg. 83(1)(g) - the other member's PIP does not lift the cap period: 2026 @@ -638,8 +647,12 @@ members: [claimant, partner] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_uc_benefit_cap_exempt: false + # HB reg. 75F(1)(ea): the claimant's partner receiving PIP lifts the HB + # cap; HB has no single claim by a member of a couple. + is_housing_benefit_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt: true - name: Reg. 83(1)(g) - joint claimants, either receiving PIP, are not capped (control) period: 2026 @@ -660,7 +673,8 @@ members: [claimant, partner] region: NORTH_EAST output: - is_benefit_cap_exempt: true + is_uc_benefit_cap_exempt: true + is_housing_benefit_benefit_cap_exempt: true - name: Reg. 83(1)(g) - the claimant's own PIP lifts the cap on a single claim period: 2026 @@ -682,7 +696,8 @@ members: [claimant, partner] region: NORTH_EAST output: - is_benefit_cap_exempt: true + is_uc_benefit_cap_exempt: true + is_housing_benefit_benefit_cap_exempt: true - name: Reg. 82(1)(a) - the couple's combined earned income, the other member's included, meets the earnings exception period: 2026 @@ -709,7 +724,11 @@ # wage of 12.71 (NMW Regs 2015 reg. 4 from 1 April 2026): # 12.71 x 16 x 52 / 12 = 881.23 a month. The model uses a fixed # 10,152 a year; 11,000 clears both. - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true + # Housing Benefit has no earnings exception (HB Regs 2006 regs. + # 75E-75F); with no working tax credit the HB cap applies. + working_tax_credit: 0 + is_housing_benefit_benefit_cap_exempt: false - name: Sch 4 para 28(2) - a renter under 35 claiming as a single person is a specified renter, whatever the partner's age period: 2026 @@ -735,6 +754,10 @@ lha_renter_has_non_dependant: false is_lha_shared_accommodation_rate_specified_renter: true LHA_category: A + # HB: a claimant with a partner is not a young individual (HB reg 2(1)); + # a couple has one bedroom (reg. 13D(3)(a)): B. + is_housing_benefit_young_individual: false + housing_benefit_LHA_category: B - name: Sch 4 para 28(2) - a renter aged 35 or over claiming as a single person is not a specified renter period: 2026 @@ -758,6 +781,8 @@ output: is_lha_shared_accommodation_rate_specified_renter: false LHA_category: B + is_housing_benefit_young_individual: false + housing_benefit_LHA_category: B - name: Sch 4 para 29(5) - only the renter's own disability benefit excepts them from the shared rate period: 2026 @@ -802,7 +827,7 @@ is_uc_single_or_joint_claimant: false is_uc_eligible: false -- name: A family that reports Housing Benefit but claims UC gets the UC single-claim rules in the cap and LHA +- name: A family that reports Housing Benefit but claims UC is single for UC's cap and shared rate and a couple for HB's period: 2026 absolute_error_margin: 0.01 input: @@ -828,18 +853,26 @@ output: claims_legacy_benefits: true uc_member_of_couple_claims_as_single_person: true - # Calculated on UC, so reg. 80A(2)(c), reg. 83(1) and Sch 4 para 28(2) - # apply: the single cap, no exception for the other member's PIP, and - # shared accommodation. - is_benefit_cap_single_claimant_rate: true - is_benefit_cap_exempt: false - benefit_cap: 14_753 + # Each scheme applies its own rules. UC: a single claimant (reg. + # 80A(2)(c)); the other member's PIP is not a claimant's, so no exception + # (reg. 83(1)(g)); shared accommodation (Sch 4 para 28(2)). + is_uc_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_exempt: false + uc_benefit_cap: 14_753 is_lha_shared_accommodation_rate_specified_renter: true LHA_category: A + # Housing Benefit has no single claim by a member of a couple: the couple + # rate (reg. 75CA(2)(d)), the partner's PIP lifts the cap (reg. + # 75F(1)(ea), "the claimant or the claimant's partner"), and a couple is + # not a young individual (reg. 2(1); reg. 13D(2)(a)(i)). + is_housing_benefit_benefit_cap_single_claimant_rate: false + is_housing_benefit_benefit_cap_exempt: true + is_housing_benefit_young_individual: false + housing_benefit_LHA_category: B # 424.90 x 12 uc_standard_allowance: 5_098.80 -- name: A family that stays on legacy benefits keeps the couple rules Housing Benefit shares +- name: A family that stays on legacy benefits gets the same per-scheme rules period: 2026 absolute_error_margin: 0.01 input: @@ -864,11 +897,19 @@ region: NORTH_EAST output: claims_legacy_benefits: true - # Housing Benefit has no single claim by a member of a couple: the couple - # rate (HB reg. 75CA), the partner's PIP lifts the cap (HB reg. - # 75F(1)(ea), "the claimant or the claimant's partner"), and a couple is - # not a young individual (HB reg. 2(1)). - is_benefit_cap_single_claimant_rate: false - is_benefit_cap_exempt: true - is_lha_shared_accommodation_rate_specified_renter: false - LHA_category: B + # Staying on legacy benefits changes neither scheme's rules. Housing + # Benefit, which this family receives, has no single claim by a member of + # a couple: the couple rate (HB reg. 75CA(2)(d)), the partner's PIP lifts + # the cap (HB reg. 75F(1)(ea)), and a couple is not a young individual + # (HB reg. 2(1)): one bedroom (B). + is_housing_benefit_benefit_cap_single_claimant_rate: false + is_housing_benefit_benefit_cap_exempt: true + is_housing_benefit_young_individual: false + housing_benefit_LHA_category: B + # The Universal Credit rules are those of the reg. 3(3) single claim + # (reg. 80A(2)(c), reg. 83(1), Sch 4 para 28(2)). + is_uc_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_exempt: false + uc_benefit_cap: 14_753 + is_lha_shared_accommodation_rate_specified_renter: true + LHA_category: A diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/dependants_income.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/dependants_income.yaml index 19bfeb3f2..76e57e1c3 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/dependants_income.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/dependants_income.yaml @@ -31,8 +31,8 @@ members: [parent, young_person] region: NORTH_EAST output: - is_benefit_cap_exempt_earnings: false - benefit_cap: 22_020 + is_uc_benefit_cap_exempt_earnings: false + uc_benefit_cap: 22_020 - name: The claimant's own earnings still meet the benefit cap earnings exception period: 2026 @@ -54,7 +54,7 @@ members: [parent, young_person] region: NORTH_EAST output: - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true - name: Joint claimants' combined earnings meet the benefit cap earnings exception # Neither partner's £6,000 reaches the threshold alone; together they do. @@ -80,7 +80,7 @@ members: [parent_1, parent_2, child] region: NORTH_EAST output: - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true - name: A dependent 18-year-old's job makes no difference to the childcare work condition # The working lone parent meets it whether or not the young person works. @@ -184,7 +184,9 @@ members: [parent, young_person] region: NORTH_EAST output: - benefit_cap: 22_020 + uc_benefit_cap: 22_020 + benefit_cap_welfare_benefits: 30_000 + uc_benefit_cap_reduction: 30_000 - 22_020 benefit_cap_reduction: 30_000 - 22_020 - name: The claimant's own contributory benefit is part of the benefit cap total @@ -209,5 +211,7 @@ members: [parent, young_person] region: NORTH_EAST output: - benefit_cap: 22_020 + uc_benefit_cap: 22_020 + benefit_cap_welfare_benefits: 30_000 + 2_000 + uc_benefit_cap_reduction: 30_000 + 2_000 - 22_020 benefit_cap_reduction: 30_000 + 2_000 - 22_020 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/legacy_closure_ineligible_partner.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/legacy_closure_ineligible_partner.yaml index c56cb9137..77a27b7ff 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/legacy_closure_ineligible_partner.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/legacy_closure_ineligible_partner.yaml @@ -45,5 +45,5 @@ claims_universal_credit: [true, true] uc_member_of_couple_claims_as_single_person: [true, true] is_lha_shared_accommodation_rate_specified_renter: [true, true] - is_benefit_cap_single_claimant_rate: [true, true] + is_uc_benefit_cap_single_claimant_rate: [true, true] universal_credit: [10_139.64, 10_139.64] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/legacy_closure_pension_route.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/legacy_closure_pension_route.yaml index 8fabfce30..1b5c7523c 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/legacy_closure_pension_route.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/legacy_closure_pension_route.yaml @@ -138,7 +138,7 @@ would_claim_uc_at_legacy_closure: true uc_maximum_amount: 1_200 uc_income_reduction: 0 - benefit_cap_reduction: 1_200 + uc_benefit_cap_reduction: 1_200 households: household: members: [older, younger] 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..806985934 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 @@ -10,7 +10,7 @@ members: person would_claim_uc: true universal_credit_pre_benefit_cap: 6_000 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: person @@ -30,7 +30,7 @@ members: person would_claim_uc: true universal_credit_pre_benefit_cap: 6_000 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 uc_latent_deduction_rate: 0.25 uc_deduction_combination: ADVANCE_ONLY households: @@ -54,7 +54,7 @@ members: person would_claim_uc: true universal_credit_pre_benefit_cap: 6_000 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 uc_latent_deduction_rate: 0.25 uc_deduction_combination: ADVANCE_ONLY households: @@ -77,7 +77,7 @@ members: person would_claim_uc: true universal_credit_pre_benefit_cap: 6_000 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 uc_latent_deduction_rate: 0.28 uc_deduction_combination: GOVERNMENT_ONLY households: @@ -100,7 +100,7 @@ members: person would_claim_uc: true universal_credit_pre_benefit_cap: 500 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 uc_latent_deduction_rate: 0.25 uc_deduction_combination: ADVANCE_ONLY households: @@ -124,7 +124,7 @@ members: person would_claim_uc: true universal_credit_pre_benefit_cap: 6_000 - benefit_cap_reduction: 0 + uc_benefit_cap_reduction: 0 households: household: members: person diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap.yaml index 9a6cf469a..6edcfdbaf 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap.yaml @@ -1,27 +1,37 @@ # UC Regulations 2013, reg 80A(2), and HB Regulations 2006, reg 75CA(2): # annual single / family caps are 14,753 / 22,020 outside London and -# 16,967 / 25,323 in London. These cases isolate rate selection from -# exemptions and do not assert eligibility to claim UC or HB. +# 16,967 / 25,323 in London. Each scheme decides who is a single claimant: +# UC a single claimant (including a member of a couple claiming as a single +# person, reg 3(3)) not responsible for a child or qualifying young person; +# HB a claimant with no partner who is not a lone parent (reg 2(1)). These +# cases isolate rate selection from exemptions and do not assert eligibility +# to claim UC or HB. - name: Single claimant aged 30 receives the outside-London single cap. period: 2025 input: age: 30 region: SOUTH_EAST - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false output: - is_benefit_cap_single_claimant_rate: true - benefit_cap: 14_753 + is_uc_benefit_cap_single_claimant_rate: true + is_housing_benefit_benefit_cap_single_claimant_rate: true + uc_benefit_cap: 14_753 + housing_benefit_benefit_cap: 14_753 - name: Single claimant aged 30 receives the London single cap. period: 2025 input: age: 30 region: LONDON - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false output: - is_benefit_cap_single_claimant_rate: true - benefit_cap: 16_967 + is_uc_benefit_cap_single_claimant_rate: true + is_housing_benefit_benefit_cap_single_claimant_rate: true + uc_benefit_cap: 16_967 + housing_benefit_benefit_cap: 16_967 - name: Single claimant aged 17 does not count as their own child outside London. period: 2025 @@ -29,14 +39,17 @@ age: 17 is_benunit_head: true region: SOUTH_EAST - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false output: is_claimant_or_partner: true is_responsible_for_child_or_qualifying_young_person_for_universal_credit: false - is_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_single_claimant_rate: true + is_housing_benefit_benefit_cap_single_claimant_rate: true # Old age counts selected 22,020; no partner or dependant means 14,753. # Change = 14,753 - 22,020 = -7,267 per year. - benefit_cap: 14_753 + uc_benefit_cap: 14_753 + housing_benefit_benefit_cap: 14_753 - name: Single claimant aged 17 does not count as their own child in London. period: 2025 @@ -44,11 +57,14 @@ age: 17 is_benunit_head: true region: LONDON - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false output: - is_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_single_claimant_rate: true + is_housing_benefit_benefit_cap_single_claimant_rate: true # London change = 16,967 - 25,323 = -8,356 per year. - benefit_cap: 16_967 + uc_benefit_cap: 16_967 + housing_benefit_benefit_cap: 16_967 - name: A 17-year-old apprentice in the claimant's unit does not give the family cap. period: 2025 @@ -66,7 +82,8 @@ benunits: family: members: [claimant, apprentice] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: home: members: [claimant, apprentice] @@ -74,10 +91,12 @@ output: is_claimant_or_partner: [true, false] is_responsible_for_child_or_qualifying_young_person_for_universal_credit: false - is_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_single_claimant_rate: true + is_housing_benefit_benefit_cap_single_claimant_rate: true # UC reg 5(3) excludes education under an employment contract. # Single cap: 14,753, down 22,020 - 14,753 = 7,267 from age-only counts. - benefit_cap: 14_753 + uc_benefit_cap: 14_753 + housing_benefit_benefit_cap: 14_753 - name: A 17-year-old outside education or approved training does not give the family cap. period: 2025 @@ -95,17 +114,20 @@ benunits: family: members: [claimant, young_person] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: home: members: [claimant, young_person] region: SOUTH_EAST output: is_responsible_for_child_or_qualifying_young_person_for_universal_credit: false - is_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_single_claimant_rate: true + is_housing_benefit_benefit_cap_single_claimant_rate: true # UC reg 5(1)(b): no qualifying education/training after the age-16 route. # Change = 14,753 - 22,020 = -7,267 per year. - benefit_cap: 14_753 + uc_benefit_cap: 14_753 + housing_benefit_benefit_cap: 14_753 - name: A 17-year-old receiving benefits in their own right does not give the family cap. period: 2025 @@ -123,17 +145,20 @@ benunits: family: members: [claimant, young_person] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: home: members: [claimant, young_person] region: SOUTH_EAST output: is_responsible_for_child_or_qualifying_young_person_for_universal_credit: false - is_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_single_claimant_rate: true + is_housing_benefit_benefit_cap_single_claimant_rate: true # UC reg 5(5) excludes own-right UC/ESA/JSA recipients from QYP status. # Change = 14,753 - 22,020 = -7,267 per year. - benefit_cap: 14_753 + uc_benefit_cap: 14_753 + housing_benefit_benefit_cap: 14_753 - name: A 16-year-old outside education gives the family cap (UC reg 5(1)(a)). period: 2025 @@ -143,21 +168,32 @@ age: 30 is_benunit_head: true is_parent: true + is_claimant_or_partner: true young_person: age: 16 is_benunit_head: false + is_claimant_or_partner: false current_education: NOT_IN_EDUCATION benunits: family: members: [claimant, young_person] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: home: members: [claimant, young_person] region: SOUTH_EAST output: + is_claimant_or_partner: [true, false] is_responsible_for_child_or_young_person_for_uc_or_housing_benefit: true - benefit_cap: 22_020 + # UC reg 5(1)(a): "up to, but not including, the 1st September following + # their 16th birthday"; + # HB reg 19 uses Child Benefit's school-leaver terminal dates. Annual ages + # cannot place either date, so both schemes give the family rate here. + is_uc_benefit_cap_single_claimant_rate: false + is_housing_benefit_benefit_cap_single_claimant_rate: false + uc_benefit_cap: 22_020 + housing_benefit_benefit_cap: 22_020 - name: A looked-after foster child does not give the claimant the family cap. period: 2025 @@ -173,18 +209,21 @@ benunits: family: members: [claimant, child] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: home: members: [claimant, child] region: SOUTH_EAST output: is_responsible_for_child_or_qualifying_young_person_for_universal_credit: false - is_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_single_claimant_rate: true + is_housing_benefit_benefit_cap_single_claimant_rate: true # UC reg 4(6)(a): this foster child is excluded from responsibility. # Assume no respite or adoption placement exception under reg 4A. # Change = 14,753 - 22,020 = -7,267 per year. - benefit_cap: 14_753 + uc_benefit_cap: 14_753 + housing_benefit_benefit_cap: 14_753 - name: Parent of a 17-year-old qualifying student keeps the family cap. period: 2025 @@ -201,7 +240,8 @@ benunits: family: members: [parent, student] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: home: members: [parent, student] @@ -209,9 +249,11 @@ output: is_couple: false is_responsible_for_child_or_qualifying_young_person_for_universal_credit: true - is_benefit_cap_single_claimant_rate: false + is_uc_benefit_cap_single_claimant_rate: false + is_housing_benefit_benefit_cap_single_claimant_rate: false # Responsibility under UC regs 4-5 gives the 22,020 annual family limit. - benefit_cap: 22_020 + uc_benefit_cap: 22_020 + housing_benefit_benefit_cap: 22_020 - name: Lone parent of an 18-year-old qualifying student keeps the London family cap. period: 2025 @@ -228,7 +270,8 @@ benunits: family: members: [parent, student] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: home: members: [parent, student] @@ -237,11 +280,13 @@ is_claimant_or_partner: [true, false] is_couple: false is_responsible_for_child_or_qualifying_young_person_for_universal_credit: true - is_benefit_cap_single_claimant_rate: false + is_uc_benefit_cap_single_claimant_rate: false + is_housing_benefit_benefit_cap_single_claimant_rate: false # Same 25,323 as before, now because of responsibility rather than two 18+ members. - benefit_cap: 25_323 + uc_benefit_cap: 25_323 + housing_benefit_benefit_cap: 25_323 -- name: Couple aged 30 and 17 claims as a single person, so the single cap applies without counting the partner as a child. +- name: Couple aged 30 and 17 claims UC as a single person, so the UC single cap applies; HB caps them as a couple. period: 2025 input: people: @@ -255,7 +300,8 @@ benunits: family: members: [claimant, partner] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: home: members: [claimant, partner] @@ -267,11 +313,16 @@ # the claimant claims as a single person (reg. 3(3)(a); ADM E2017). uc_is_ineligible_partner: [false, true] uc_member_of_couple_claims_as_single_person: true - is_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_single_claimant_rate: true # Reg. 80A(2)(c): 14,753 for a single claimant outside Greater London # who is not responsible for a child or qualifying young person (ADM # E5007 note 3 applies it to a member of a couple claiming as single). - benefit_cap: 14_753 + uc_benefit_cap: 14_753 + # HB has no single claim by a member of a couple: the claimant has a + # partner, so is not a "single claimant" (HB reg 2(1)), and reg. + # 75CA(2)(d) gives 22,020 outside Greater London. + is_housing_benefit_benefit_cap_single_claimant_rate: false + housing_benefit_benefit_cap: 22_020 - name: Couple aged 30 and 17 where the partner is within reg. 8 claim jointly and receive the couple cap. period: 2025 @@ -290,7 +341,8 @@ benunits: family: members: [claimant, partner] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: home: members: [claimant, partner] @@ -298,9 +350,11 @@ output: uc_is_ineligible_partner: [false, false] uc_member_of_couple_claims_as_single_person: false - is_benefit_cap_single_claimant_rate: false + is_uc_benefit_cap_single_claimant_rate: false + is_housing_benefit_benefit_cap_single_claimant_rate: false # Reg. 80A(2)(d)(i): 22,020 for joint claimants outside Greater London. - benefit_cap: 22_020 + uc_benefit_cap: 22_020 + housing_benefit_benefit_cap: 22_020 - name: Explicitly identified 19-year-old non-dependant does not imply a partner. period: 2025 @@ -316,7 +370,8 @@ benunits: family: members: [claimant, non_dependant] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: home: members: [claimant, non_dependant] @@ -324,29 +379,47 @@ output: is_couple: false is_responsible_for_child_or_qualifying_young_person_for_universal_credit: false - is_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_single_claimant_rate: true + is_housing_benefit_benefit_cap_single_claimant_rate: true # Old two-adult count gave 22,020. With explicit roles, no partner or QYP: # change = 14,753 - 22,020 = -7,267 per year. - benefit_cap: 14_753 + uc_benefit_cap: 14_753 + housing_benefit_benefit_cap: 14_753 -- name: Benefit-cap exemption still overrides the selected single rate. +- name: A UC benefit-cap exemption still overrides the selected single rate. period: 2025 input: age: 30 region: SOUTH_EAST - is_benefit_cap_exempt: true + is_uc_benefit_cap_exempt: true universal_credit_pre_benefit_cap: 100_000 output: - is_benefit_cap_single_claimant_rate: true + is_uc_benefit_cap_single_claimant_rate: true # Exemption makes the cap infinite: max(100,000 - infinity, 0) = 0. # Assert the resulting reduction because the YAML runner cannot compare infinities. + uc_benefit_cap_reduction: 0 + benefit_cap_reduction: 0 + +- name: An HB benefit-cap exemption still overrides the selected single rate. + period: 2025 + input: + age: 30 + region: SOUTH_EAST + is_housing_benefit_benefit_cap_exempt: true + housing_benefit_pre_benefit_cap: 100_000 + would_claim_uc: false + output: + is_housing_benefit_benefit_cap_single_claimant_rate: true + housing_benefit_benefit_cap_reduction: 0 benefit_cap_reduction: 0 -- name: A Housing Benefit young person keeps the family cap though not a UC qualifying young person +- name: A Housing Benefit young person gives the HB family cap but not the UC one # The 19-year-old started non-advanced education at 17 and is past UC's # terminal date, so is not a UC qualifying young person (UC reg 5), but is a # Child Benefit qualifying young person, which Housing Benefit uses (HB reg - # 19). One cap serves both, so the family rate applies: 22,020. + # 19). HB: a lone parent, so the family rate (reg 75CA(2)(d)): 22,020. UC: a + # single claimant not responsible for a child or qualifying young person + # (reg 80A(2)(c)): 14,753. period: 2025 input: people: @@ -361,7 +434,8 @@ benunits: benunit: members: [parent, student] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: household: members: [parent, student] @@ -369,11 +443,16 @@ output: is_qualifying_young_person_for_universal_credit: [false, false] is_qualifying_young_person_for_child_benefit: [false, true] - benefit_cap: 22_020 + is_uc_benefit_cap_single_claimant_rate: true + is_housing_benefit_benefit_cap_single_claimant_rate: false + uc_benefit_cap: 14_753 + housing_benefit_benefit_cap: 22_020 -- name: A 16-year-old outside education keeps the family cap (UC reg 5(1)(a) window) +- name: A 16-year-old outside education keeps the family cap (UC reg 5(1)(a), Child Benefit regs 5 and 7) # Every 16-year-old is a UC qualifying young person until the 1 September - # after their 16th birthday; annual ages cannot place that date. + # after their 16th birthday, and a school leaver stays a Child Benefit + # qualifying young person (HB reg 19) until a terminal date; annual ages + # cannot place those dates, so both schemes give the family rate. period: 2025 input: people: @@ -386,10 +465,12 @@ benunits: benunit: members: [parent, teenager] - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false households: household: members: [parent, teenager] region: SOUTH_EAST output: - benefit_cap: 22_020 + uc_benefit_cap: 22_020 + housing_benefit_benefit_cap: 22_020 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_capped_total.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_capped_total.yaml index 2cccf2248..eebe41970 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_capped_total.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_capped_total.yaml @@ -36,8 +36,13 @@ output: esa_income: 5_000 claimant_or_partner_esa_income: 0 - is_benefit_cap_exempt: false - # 20,000 - 14,753 (the adult's 5,000 is theirs, not the family's) + benefit_cap_welfare_benefits: 20_000 + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false + # UC reg 81: max(20,000 - 14,753 - 0 childcare, 0) = 5,247. + # The adult's 5,000 is theirs, not the family's; there is no HB award. + uc_benefit_cap_reduction: 5_247 + housing_benefit_benefit_cap_reduction: 0 benefit_cap_reduction: 5_247 universal_credit: 14_753 @@ -65,8 +70,12 @@ members: [claimant, adult] region: NORTH_EAST output: - is_benefit_cap_exempt: false - # 18,000 - 14,753 + benefit_cap_welfare_benefits: 18_000 + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false + # UC reg 81: max(18,000 - 14,753 - 0 childcare, 0) = 3,247. + uc_benefit_cap_reduction: 3_247 + housing_benefit_benefit_cap_reduction: 0 benefit_cap_reduction: 3_247 universal_credit: 14_753 @@ -86,7 +95,10 @@ members: [claimant] region: NORTH_EAST output: - # 15,000 + 3,000 - 14,753 + benefit_cap_welfare_benefits: 18_000 + # UC reg 81: max(15,000 + 3,000 - 14,753 - 0 childcare, 0) = 3,247. + uc_benefit_cap_reduction: 3_247 + housing_benefit_benefit_cap_reduction: 0 benefit_cap_reduction: 3_247 universal_credit: 11_753 @@ -114,7 +126,11 @@ region: NORTH_EAST output: claimant_or_partner_jsa_income: 0 - # 9,000 is under 14,753 + benefit_cap_welfare_benefits: 9_000 + # HB reg 75D: min(max(9,000 - 14,753, 0), 9,000 - 0.50 * 52) = 0. + # No UC award: UC reg 78(1) gives no UC reduction. + uc_benefit_cap_reduction: 0 + housing_benefit_benefit_cap_reduction: 0 benefit_cap_reduction: 0 housing_benefit: 9_000 @@ -137,17 +153,21 @@ region: NORTH_EAST output: claimant_or_partner_jsa_income: 6_000 - # 9,000 + 6,000 - 14,753 (HB Regs 2006 reg 75D(1)) + benefit_cap_welfare_benefits: 15_000 + # HB reg 75D: min(9,000 + 6,000 - 14,753, 9,000 - 0.50 * 52) = 247. + # HB left = 9,000 - 247 = 8,753, above the reg 75 minimum of 26/year. + uc_benefit_cap_reduction: 0 + housing_benefit_benefit_cap_reduction: 247 benefit_cap_reduction: 247 housing_benefit: 8_753 -# Whose benefits: the couple of the scheme the cap reduces. For a family on -# Universal Credit, its claimants (is_uc_claimant, or the claimant and -# partner unless entered; UC Regs 2013 regs 78(2), 80(1)); otherwise Housing -# Benefit's claimant and partner (HB Regs 2006 reg 75A). Here the second -# adult is entered as a Universal Credit claimant but is not the Housing -# Benefit partner. An input entered for one person takes its default for the -# others, so both claimants are entered. +# Whose benefits: the couple of the scheme whose award the family has +# (benefit_cap_couple). For a family on Universal Credit, its claimants +# (is_uc_claimant, or the claimant and partner unless entered; UC Regs 2013 +# regs 78(2), 80(1)); otherwise Housing Benefit's claimant and partner (HB +# Regs 2006 reg 75A). Here the second adult is entered as a Universal Credit +# claimant but is not the Housing Benefit partner. An input entered for one +# person takes its default for the others, so both claimants are entered. - name: On Universal Credit, an entered joint claimant's own contributory JSA is in the capped total period: 2025 @@ -175,9 +195,12 @@ output: is_uc_assessed_claimant: [true, true] # Joint claimants outside London: 22,020 (UC Regs 2013 reg 80A(2)(d)(i)). - is_benefit_cap_single_claimant_rate: false - benefit_cap: 22_020 - # 25,000 + 2,000 - 22,020. + is_uc_benefit_cap_single_claimant_rate: false + uc_benefit_cap: 22_020 + benefit_cap_welfare_benefits: 27_000 + # 25,000 + 2,000 - 22,020, less a childcare element of 0 (reg 81). + uc_benefit_cap_reduction: 4_980 + housing_benefit_benefit_cap_reduction: 0 benefit_cap_reduction: 4_980 - name: On Universal Credit, an entered joint claimant's own income-based JSA is in the capped total @@ -206,8 +229,11 @@ output: jsa_income: 2_000 claimant_or_partner_jsa_income: 0 - benefit_cap: 22_020 - # 25,000 + 2,000 - 22,020. + uc_benefit_cap: 22_020 + benefit_cap_welfare_benefits: 27_000 + # 25,000 + 2,000 - 22,020, less a childcare element of 0 (reg 81). + uc_benefit_cap_reduction: 4_980 + housing_benefit_benefit_cap_reduction: 0 benefit_cap_reduction: 4_980 - name: On Housing Benefit alone, the same adult's contributory JSA is not in the capped total @@ -235,12 +261,18 @@ region: NORTH_EAST output: # The adult is not the claimant's partner (HB Regs 2006 reg 75A), so - # the single rate and none of the adult's benefits: 15,000 - 14,753. - benefit_cap: 14_753 + # the single rate (reg 75CA(2)) and none of the adult's benefits: + # 15,000 - 14,753, which leaves more than reg 75's 50p a week. + housing_benefit_benefit_cap: 14_753 + benefit_cap_welfare_benefits: 15_000 + uc_benefit_cap_reduction: 0 + housing_benefit_benefit_cap_reduction: 247 benefit_cap_reduction: 247 -# The rate's couple: is_couple (relation_type, which can be entered), unless -# the entered claimants of a Universal Credit award are other members. +# The rate's couple: is_couple (relation_type, which can be entered), unless, +# for Universal Credit, the entered claimants of the award are other members +# (is_uc_benefit_cap_single_claimant_rate). Housing Benefit reads its own +# claimant and partner (is_housing_benefit_benefit_cap_single_claimant_rate). - name: On Housing Benefit alone, a couple entered as such has the couple limit though the model would presume the younger member a child period: 2025 @@ -264,9 +296,10 @@ output: is_claimant_or_partner: [true, false] is_couple: true - # A couple outside London: 22,020, as is_couple gives. - is_benefit_cap_single_claimant_rate: false - benefit_cap: 22_020 + # A couple outside London: 22,020 (HB Regs 2006 reg 75CA(2)), as + # relation_type gives (is_single_person). + is_housing_benefit_benefit_cap_single_claimant_rate: false + housing_benefit_benefit_cap: 22_020 benefit_cap_reduction: 0 - name: On Universal Credit, a couple entered as such has the couple limit when no claimant is entered @@ -291,6 +324,6 @@ output: is_couple: true # Joint claimants outside London: 22,020 (UC Regs 2013 reg 80A(2)(d)(i)). - is_benefit_cap_single_claimant_rate: false - benefit_cap: 22_020 + is_uc_benefit_cap_single_claimant_rate: false + uc_benefit_cap: 22_020 benefit_cap_reduction: 0 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_earnings_exception_earned_income.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_earnings_exception_earned_income.yaml index 2ad812620..ae03b52bd 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_earnings_exception_earned_income.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_earnings_exception_earned_income.yaml @@ -25,7 +25,7 @@ region: NORTH_EAST output: uc_individual_earned_income_before_mif: 11_000 - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true benefit_cap_reduction: 0 - name: Statutory paternity pay is earned income for the earnings exception @@ -38,7 +38,7 @@ region: NORTH_EAST output: uc_individual_earned_income_before_mif: 11_000 - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true benefit_cap_reduction: 0 - name: Statutory maternity pay is earned income for the earnings exception @@ -62,8 +62,8 @@ # threshold, so no tax or NI: 11,000 >= 10,152. Without the maternity # pay the wages alone, 3,000, are under the threshold. uc_individual_earned_income_before_mif: 11_000 - is_benefit_cap_exempt_earnings: true - is_benefit_cap_exempt: true + is_uc_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt: true benefit_cap_reduction: 0 - name: Tax on a private pension does not come off the earnings the exception tests @@ -93,7 +93,7 @@ income_tax: 1_686 uc_income_tax_on_earnings: 0 uc_individual_earned_income_before_mif: 11_000 - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true benefit_cap_reduction: 0 - name: Relievable pension contributions come off the earnings the exception tests @@ -116,8 +116,8 @@ # Reg 55(5)(a): 11,000 - 1,000 = 10,000, with no tax or NI, is under # 10,152. The cap applies: 18,000 - 14,753 = 3,247. uc_individual_earned_income_before_mif: 10_000 - is_benefit_cap_exempt_earnings: false - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt_earnings: false + is_uc_benefit_cap_exempt: false benefit_cap_reduction: 3_247 - name: The minimum income floor does not create an earnings exception @@ -132,7 +132,7 @@ output: uc_individual_earned_income_before_mif: 5_000 uc_individual_earned_income: 20_000 - is_benefit_cap_exempt_earnings: false + is_uc_benefit_cap_exempt_earnings: false benefit_cap_reduction: 3_247 - name: Means-tested basic income counts toward the couple's earnings exception @@ -166,7 +166,7 @@ # outside London (22,020), so without the exemption the cap would take # 2,980; with it, nothing. uc_individual_earned_income_before_mif: [5_500, 5_500] - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true benefit_cap_reduction: 0 - name: Without the basic income reform toggle, basic income does not count toward the earnings exception @@ -195,5 +195,5 @@ # applies and the couple cap outside London (22,020) takes # 25,000 - 22,020 = 2,980. uc_individual_earned_income_before_mif: [0, 0] - is_benefit_cap_exempt_earnings: false + is_uc_benefit_cap_exempt_earnings: false benefit_cap_reduction: 2_980 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml index b0232bd97..0471cc748 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_pension_age.yaml @@ -40,9 +40,9 @@ output: is_uc_entitled: true housing_benefit_pension_age_regulations_apply: false - is_benefit_cap_exempt_other: false - is_benefit_cap_exempt: false - benefit_cap: 25_323 + is_uc_benefit_cap_exempt_qualifying_age: false + is_uc_benefit_cap_exempt: false + uc_benefit_cap: 25_323 # 30,000 + 3,000 - 25,323 benefit_cap_reduction: 7_677 universal_credit: 22_323 @@ -72,9 +72,9 @@ members: [older, younger, child_1, child_2] region: LONDON output: - is_benefit_cap_exempt_other: false - is_benefit_cap_exempt_earnings: true - is_benefit_cap_exempt: true + is_uc_benefit_cap_exempt_qualifying_age: false + is_uc_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt: true benefit_cap_reduction: 0 universal_credit: 30_000 @@ -103,7 +103,7 @@ output: is_uc_entitled: false housing_benefit_pension_age_regulations_apply: true - is_benefit_cap_exempt_other: true + is_housing_benefit_benefit_cap_exempt: true benefit_cap_reduction: 0 housing_benefit: 30_000 @@ -134,9 +134,8 @@ output: jsa_income: 4_000 housing_benefit_pension_age_regulations_apply: false - is_benefit_cap_exempt_other: false - is_benefit_cap_exempt: false - benefit_cap: 25_323 + is_housing_benefit_benefit_cap_exempt: false + housing_benefit_benefit_cap: 25_323 # 30,000 + 4,000 - 25,323 benefit_cap_reduction: 8_677 housing_benefit: 21_323 @@ -162,7 +161,7 @@ output: is_uc_entitled: false housing_benefit_pension_age_regulations_apply: true - is_benefit_cap_exempt_other: true + is_housing_benefit_benefit_cap_exempt: true benefit_cap_reduction: 0 housing_benefit: 30_000 @@ -191,16 +190,19 @@ region: LONDON output: housing_benefit_pension_age_regulations_apply: false - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt: false benefit_cap_reduction: 7_677 universal_credit: 22_323 - name: A pensioner whose only younger adult is a qualifying young person is not capped # Universal Credit needs a claimant or partner under the qualifying age (UC # Regs 2013 reg 3(2)(a)), and the model's is_uc_eligible needs one too, so - # the award here is an input the family could not hold in law. The - # 18-year-old is a dependant, not a partner, so the award does not take the - # family out of the pension-age Housing Benefit rules, which have no cap. + # the award here is an input. The only claim a claimant over that age can + # make is a qualifying claim by a tax credit migrant (SI 2014/1230 reg + # 60A), and SI 2014/1230 reg 60C disapplies the cap for it. The + # 18-year-old is a dependant, not a partner, so the parent is the only + # claimant, and the award does not take the family out of the pension-age + # Housing Benefit rules, which have no cap. period: 2026 absolute_error_margin: 0.01 input: @@ -227,7 +229,9 @@ is_claimant_or_partner: [true, false, false] is_uc_entitled: true housing_benefit_pension_age_regulations_apply: true - is_benefit_cap_exempt_other: true + is_uc_benefit_cap_exempt_qualifying_age: true + is_uc_benefit_cap_exempt: true + is_housing_benefit_benefit_cap_exempt: true benefit_cap_reduction: 0 universal_credit: 30_000 @@ -260,7 +264,8 @@ output: is_claimant_or_partner: [true, false, false] housing_benefit_pension_age_regulations_apply: true - is_benefit_cap_exempt_other: true - is_benefit_cap_exempt: true + is_uc_benefit_cap_exempt_qualifying_age: true + is_uc_benefit_cap_exempt: true + is_housing_benefit_benefit_cap_exempt: true benefit_cap_reduction: 0 universal_credit: 30_000 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_specified_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_specified_benefits.yaml index a68fd5ba4..a33fcf457 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_specified_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_specified_benefits.yaml @@ -17,23 +17,33 @@ # support payment". Reg 75F(2)(a): a war pension includes "a guaranteed # income payment". # -# UC Regs 2013 (SI 2013/376) reg 83(1) draws the same lines with "a -# claimant", adding "(a) the LCWRA element is included in the award" and -# "(j) the carer element is included in the award". The model applies one -# cap to both schemes, so either scheme's exception counts. +# UC Regs 2013 (SI 2013/376) reg 83(1) refers to "a claimant" and adds +# "the LCWRA element is included in the award" (a) and "the carer element +# is included in the award of universal credit" (j). HB reg 75F lists +# specified benefits, without exceptions for those UC elements. Each +# scheme has its own cap and exemptions; common cases assert both. +# +# DLA: UC reg 83(1)(f) includes "a child or qualifying young person"; +# HB reg 75F(1)(e) includes "a child or young person". PIP, CA and CSP: +# UC reg 83(1)(g), (i), (ia) includes "a qualifying young person"; HB reg +# 75F(1)(ea), (h), (ha) includes "a young person". Their definitions are +# UC regs 4-5 and HB regs 2(1), 19 respectively. # # 2025-26 caps (UC reg 80A(2); HB reg 75CA): single outside London 14,753; # couple or family outside London 22,020. Each family lives in the North # East. Benefit amounts are set directly, so the expected values follow from -# the cap arithmetic alone: reduction = capped benefits - cap (UC reg 81; -# HB reg 75D). +# the cap arithmetic alone: UC reg 81 deducts the excess less the childcare +# element from a UC award; HB reg 75D deducts the excess from HB, leaving +# at least 50p a week. The shared reduction is the two schemes' sum. +# Where neither scheme has an award, both reductions and their sum are 0. # # A "non-dependent adult" below is a benefit-unit member aged 30 (50 in the # attendance allowance case) who is neither the claimant, the partner, nor a # child or young person they are responsible for. Their own benefits and -# circumstances never lift the family's cap, and nor do their age or -# earnings: the age exception reads the claimant and partner (Housing -# Benefit's pension-age regulations, HB Regs 2006 reg 5) and the earnings +# circumstances never lift either scheme's specified-benefit exception, and +# nor do their age or earnings: Housing Benefit's age exception reads the +# claimant and partner (its pension-age regulations, HB Regs 2006 reg 5), +# Universal Credit's the claimants (SI 2014/1230 reg 60C), and the earnings # exception the claimants (UC Regs 2013 reg 82(1)(a)). # --- ESA with the support component: HB reg 75F(1)(a), UC reg 83(1)(a) --- @@ -55,8 +65,10 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true - is_benefit_cap_exempt: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true + is_uc_benefit_cap_exempt: true + is_housing_benefit_benefit_cap_exempt: true benefit_cap_reduction: 0 universal_credit: 18_000 @@ -77,9 +89,11 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false - is_benefit_cap_exempt: false - benefit_cap: 14_753 + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false + uc_benefit_cap: 14_753 # 18,000 UC + 7,000 ESA - 14,753 = 10,247 benefit_cap_reduction: 10_247 # 18,000 - 10,247 @@ -105,7 +119,8 @@ region: NORTH_EAST output: esa_income: 6_000 - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true benefit_cap_reduction: 0 housing_benefit: 9_000 @@ -129,8 +144,9 @@ region: NORTH_EAST output: esa_income: 6_000 - is_benefit_cap_exempt_health_disability: false - benefit_cap: 14_753 + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false + housing_benefit_benefit_cap: 14_753 # 9,000 HB + 6,000 ESA - 14,753 = 247 (HB reg 75D(1)) benefit_cap_reduction: 247 housing_benefit: 8_753 @@ -157,7 +173,8 @@ region: NORTH_EAST output: is_couple: true - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true benefit_cap_reduction: 0 - name: Partner's contributory ESA without the support component leaves the couple capped @@ -181,8 +198,9 @@ members: [claimant, partner] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false - benefit_cap: 22_020 + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false + uc_benefit_cap: 22_020 # 18,000 + 7,000 - 22,020 = 2,980 benefit_cap_reduction: 2_980 @@ -208,10 +226,12 @@ members: [claimant, adult] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false - is_benefit_cap_exempt_other: false - is_benefit_cap_exempt: false - benefit_cap: 14_753 + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false + housing_benefit_pension_age_regulations_apply: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false + uc_benefit_cap: 14_753 # Their 7,000 is theirs, not in the family's capped total: # 18,000 - 14,753. benefit_cap_reduction: 3_247 @@ -227,8 +247,8 @@ esa_includes_support_component: true # Isolate the ESA head. Universal Credit would still give this person # limited capability for work-related activity from the ESA - # assessment (UC Regs 2013 reg 40(1)(a)(ii)), and the model's one cap - # takes UC reg 83(1)(a) for Housing Benefit families too. + # assessment (UC Regs 2013 reg 40(1)(a)(ii)), which could lift the UC + # cap under reg 83(1)(a). HB reg 75F has no LCWRA-element exception. uc_limited_capability_for_WRA: false benunits: family: @@ -242,7 +262,8 @@ output: esa_income: 0 is_on_income_related_esa: false - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: With no support-component input, a person's own contributory ESA is taken to include it period: 2025 @@ -261,7 +282,8 @@ region: NORTH_EAST output: esa_includes_support_component: true - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true benefit_cap_reduction: 0 - name: A person without ESA has no support component @@ -279,7 +301,8 @@ region: NORTH_EAST output: esa_includes_support_component: false - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: An income-related award entered for the family is the claimant's, with the support component period: 2025 @@ -301,7 +324,8 @@ output: is_on_income_related_esa: true esa_includes_support_component: true - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true housing_benefit: 9_000 - name: A partner who reports the couple's income-related ESA has the support component @@ -330,7 +354,8 @@ # the person whose allowance it is). is_on_income_related_esa: [false, true] esa_includes_support_component: [false, true] - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: An ESA recipient without the support component has no LCWRA, whatever their disability flag period: 2025 @@ -354,7 +379,8 @@ # capability determination, which found limited capability for work only. uc_limited_capability_for_WRA: false uc_LCWRA_element: 0 - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false # 18,000 + 7,000 - 14,753 benefit_cap_reduction: 10_247 @@ -398,7 +424,8 @@ output: uc_limited_capability_for_WRA: false # The ESA head still lifts the cap. - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: An ESA recipient without the support component, flagged disabled, keeps limited capability for work period: 2025 input: @@ -487,7 +514,8 @@ region: NORTH_EAST output: uc_limited_capability_for_WRA: false - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false # 9,000 + 6,000 - 14,753 benefit_cap_reduction: 247 @@ -517,7 +545,8 @@ is_on_income_related_esa: [true, false] has_own_esa_award: [true, false] esa_includes_support_component: [true, false] - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: Without ESA, LCWRA still follows the disability flag period: 2025 @@ -594,7 +623,8 @@ members: [claimant, partner] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A young person's industrial injuries benefit does not lift the cap period: 2025 @@ -616,7 +646,8 @@ region: NORTH_EAST output: is_child_or_young_person_for_legacy_benefits: [false, true] - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A non-dependent adult's industrial injuries benefit does not lift the cap period: 2025 @@ -638,7 +669,8 @@ members: [claimant, adult] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: Claimant's attendance allowance lifts the cap period: 2025 @@ -655,7 +687,8 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A non-dependent adult's attendance allowance does not lift the cap period: 2025 @@ -677,7 +710,8 @@ members: [claimant, adult] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: Claimant's armed forces compensation payment lifts the cap period: 2025 @@ -694,7 +728,8 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A non-dependent adult's armed forces compensation payment does not lift the cap period: 2025 @@ -716,9 +751,11 @@ members: [claimant, adult] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false - is_benefit_cap_exempt_other: false - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false + housing_benefit_pension_age_regulations_apply: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false # --- Disability living allowance: HB reg 75F(1)(e), UC reg 83(1)(f) --- @@ -740,7 +777,8 @@ members: [parent, child] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A young person's DLA lifts the cap period: 2025 @@ -761,7 +799,8 @@ members: [parent, young_person] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A 16-year-old's DLA lifts the cap whatever their education (UC reg 5(1)(a)) period: 2025 @@ -783,7 +822,12 @@ region: NORTH_EAST output: is_child_or_young_person_for_legacy_benefits: [false, false] - is_benefit_cap_exempt_health_disability: true + # UC reg 5(1)(a): "up to, but not including, the 1st September following + # their 16th birthday". Annual ages count the 16-year-old for the year. + # HB reg 19(1) requires a Child Benefit "qualifying young person"; + # this person is outside education and is not one in the model. + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A 19-year-old outside education is not a young person, so their DLA does not lift the cap period: 2025 @@ -806,7 +850,8 @@ output: is_claimant_or_partner: [true, false] is_child_or_young_person_for_legacy_benefits: [false, false] - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A looked-after child's DLA does not lift the cap period: 2025 @@ -826,7 +871,8 @@ members: [claimant, child] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A non-dependent adult's DLA does not lift the cap period: 2025 @@ -849,9 +895,11 @@ members: [claimant, adult] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false - is_benefit_cap_exempt: false - benefit_cap: 14_753 + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false + uc_benefit_cap: 14_753 # 18,000 - 14,753 benefit_cap_reduction: 3_247 @@ -877,7 +925,8 @@ members: [parent, young_person] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: Claimant's PIP lifts the cap period: 2025 @@ -894,7 +943,8 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A non-dependent adult's PIP does not lift the cap period: 2025 @@ -916,7 +966,8 @@ members: [claimant, adult] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: Claimant's armed forces independence payment lifts the cap period: 2025 @@ -933,7 +984,8 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A non-dependent adult's armed forces independence payment does not lift the cap period: 2025 @@ -955,7 +1007,8 @@ members: [claimant, adult] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A young person's Carer's Allowance lifts the cap period: 2025 @@ -976,7 +1029,8 @@ members: [parent, young_person] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A young person's Carer Support Payment lifts the cap period: 2025 @@ -998,7 +1052,8 @@ country: SCOTLAND region: SCOTLAND output: - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A non-dependent adult's Carer's Allowance and caring do not lift the cap period: 2025 @@ -1022,7 +1077,8 @@ region: NORTH_EAST output: is_carer_for_benefits: [false, true] - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A young person's armed forces independence payment lifts the cap (HB reg 75F(1)(ea)) period: 2025 @@ -1043,7 +1099,12 @@ members: [parent, young_person] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + # UC reg 83(1)(c): "a claimant is receiving attendance allowance"; + # reg 2 includes AFIP in attendance allowance, so this young person + # does not lift the UC cap. HB reg 75F(1)(ea) includes "a young person" + # receiving "an armed forces independence payment". + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A 16-year-old out of education with armed forces independence payment does not lift the cap period: 2025 @@ -1066,7 +1127,8 @@ output: # Not a Housing Benefit young person (not in education), and Universal # Credit counts the payment for a claimant only (UC Regs 2013 reg 2). - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A 16-year-old receiving benefits in their own right is not a young person, so their DLA does not lift the cap period: 2025 @@ -1089,7 +1151,8 @@ region: NORTH_EAST output: # UC Regs 2013 reg 5(5); HB Regs 2006 reg 19(2). - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false # --- UC LCWRA and carer elements: UC reg 83(1)(a), (j); regs 27(1), 29(1) --- @@ -1108,7 +1171,10 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + # UC reg 83(1)(a): "the LCWRA element is included in the award". + # HB reg 75F(1) lists specified benefits, without a UC LCWRA element. + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A non-dependent adult's limited capability for work-related activity does not lift the cap period: 2025 @@ -1130,7 +1196,8 @@ members: [claimant, adult] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A disabled child's limited capability does not lift the cap without DLA period: 2025 @@ -1150,7 +1217,8 @@ members: [parent, child] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A claimant caring 40 hours a week lifts the cap through the UC carer element period: 2025 @@ -1170,7 +1238,10 @@ output: carers_allowance: 0 is_carer_for_benefits: true - is_benefit_cap_exempt_health_disability: true + # UC reg 83(1)(j): "the carer element is included in the award of + # universal credit". HB reg 75F has no UC-carer-element exception. + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: false - name: A non-dependent adult with every exempting benefit and circumstance does not lift the cap period: 2025 @@ -1204,10 +1275,13 @@ members: [claimant, adult] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: false - is_benefit_cap_exempt_other: false - is_benefit_cap_exempt: false - benefit_cap: 14_753 + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false + housing_benefit_pension_age_regulations_apply: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false + uc_benefit_cap: 14_753 + housing_benefit_benefit_cap: 14_753 - name: A carer element entered for the award lifts the cap (UC reg 83(1)(j)) period: 2025 @@ -1225,7 +1299,10 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + # UC reg 83(1)(j): "the carer element is included in the award of + # universal credit". HB reg 75F has no UC-carer-element exception. + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: false benefit_cap_reduction: 0 - name: An LCWRA element entered for the award lifts the cap (UC reg 83(1)(a)) @@ -1244,7 +1321,10 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + # UC reg 83(1)(a): "the LCWRA element is included in the award". + # HB reg 75F(1) lists specified benefits, without a UC LCWRA element. + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: false benefit_cap_reduction: 0 - name: An LCWRA element entered for the award lifts the cap whatever a non-dependant's limited capability @@ -1272,7 +1352,8 @@ output: # The award includes the element (reg 83(1)(a)); who else in the benefit # unit has limited capability does not matter. - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: false benefit_cap_reduction: 0 - name: A carer element entered for the award lifts the cap whatever a non-dependant's caring @@ -1298,7 +1379,8 @@ region: NORTH_EAST output: # Reg 83(1)(j): the award includes the carer element. - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: false benefit_cap_reduction: 0 - name: An LCWRA element entered as nil means the award does not include it, though the claimant has limited capability @@ -1320,7 +1402,8 @@ output: # Reg 83(1)(a) asks whether the element is included in the award (for # example, not during the reg 28 waiting period). 18,000 - 14,753. - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false benefit_cap_reduction: 3_247 - name: A non-dependant's limited capability does not lift the cap when no element is entered @@ -1348,7 +1431,8 @@ # No claimant has limited capability (reg 27(1)), so the element the # model calculates from the adult's flag is not a claimant's: 18,000 - # 14,753. - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false benefit_cap_reduction: 3_247 - name: From 2026 too, a non-dependant's limited capability does not lift the cap when no element is entered @@ -1376,7 +1460,8 @@ # The Universal Credit rebalancing rescales the element the model # calculates from the adult's flag (scenarios/uc_reform.py); it is still # not a claimant's element (reg 27(1)). 18,000 - 14,753. - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false benefit_cap_reduction: 3_247 - name: From 2026 too, an LCWRA element entered for the award lifts the cap whatever a non-dependant's limited capability @@ -1404,7 +1489,8 @@ output: # The rebalancing may rescale an entered element, but it stays the # award's (reg 83(1)(a)). - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: false benefit_cap_reduction: 0 - name: A carer element the family's entered carer flag gives lifts the cap, though no member is flagged @@ -1425,7 +1511,8 @@ output: # The award includes the carer element (reg 83(1)(j)), and no member who # is not a claimant has caring responsibilities to give it instead. - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: false benefit_cap_reduction: 0 - name: A nil carer element is not included in the award, though the claimant cares @@ -1448,7 +1535,8 @@ # Reg 83(1)(j) asks whether the award includes the element; the # family's entered flag gives none. 18,000 - 14,753. uc_carer_element: 0 - is_benefit_cap_exempt_health_disability: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false benefit_cap_reduction: 3_247 # --- Working tax credit: HB reg 75E(2) --- @@ -1468,9 +1556,16 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_health_disability: true + # HB reg 75E(2): "the claimant is, or the claimant and the claimant's + # partner are jointly, entitled to working tax credit". WTC is outside + # HB reg 75F and UC reg 83, and lifts only the HB cap through reg 75E. + is_uc_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_working_tax_credit: true + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: true -# --- The other two exception variables keep their results --- +# --- UC earnings (reg 82) and HB pension-age regulations (reg 5) --- - name: Earnings of 20,000 a year meet the model's earnings exception period: 2025 @@ -1489,7 +1584,7 @@ output: # Income tax (20,000 - 12,570) x 20% = 1,486; NI (20,000 - 12,570) x 8% # = 594.40; 20,000 - 1,486 - 594.40 = 17,919.60 >= 10,152. - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true - name: Earnings of 9,000 a year do not meet the model's earnings exception period: 2025 @@ -1507,7 +1602,7 @@ region: NORTH_EAST output: # Below the personal allowance and primary threshold: 9,000 < 10,152. - is_benefit_cap_exempt_earnings: false + is_uc_benefit_cap_exempt_earnings: false - name: A claimant over the qualifying age for State Pension Credit and off Universal Credit is outside the cap (HB pension-age regulations) period: 2025 @@ -1523,8 +1618,17 @@ members: [claimant] region: NORTH_EAST output: - is_benefit_cap_exempt_other: true - is_benefit_cap_exempt: true + # HB reg 5(3): "these Regulations shall not apply ... if he ... has + # attained the qualifying age for state pension credit" (subject to + # reg 5(1)(b)); the pension-age regulations contain no benefit cap. + # Universal Credit's cap has no age exception for a claimant (UC reg + # 79(1) names only the exceptions "in regulation 82 ... or 83"), but SI + # 2014/1230 reg 60C disapplies it where every claimant has reached the + # qualifying age. + housing_benefit_pension_age_regulations_apply: true + is_uc_benefit_cap_exempt_qualifying_age: true + is_uc_benefit_cap_exempt: true + is_housing_benefit_benefit_cap_exempt: true - name: A non-dependent adult over State Pension age does not lift the cap period: 2025 @@ -1545,11 +1649,13 @@ members: [claimant, adult] region: NORTH_EAST output: - # The pension-age regulations read the claimant and partner (HB Regs - # 2006 reg 5); Universal Credit has no age exception for a working-age - # claimant. 18,000 - 14,753. - is_benefit_cap_exempt_other: false - is_benefit_cap_exempt: false + # Housing Benefit's pension-age regulations read the claimant and + # partner (HB Regs 2006 reg 5), and SI 2014/1230 reg 60C the claimants; + # Universal Credit has no other age exception. 18,000 - 14,753. + housing_benefit_pension_age_regulations_apply: false + is_uc_benefit_cap_exempt_qualifying_age: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false benefit_cap_reduction: 3_247 - name: A non-dependent adult's earnings do not meet the earnings exception @@ -1575,6 +1681,7 @@ output: # UC Regs 2013 reg 82(1)(a) reads the claimant's earned income. 18,000 - # 14,753. - is_benefit_cap_exempt_earnings: false - is_benefit_cap_exempt: false + is_uc_benefit_cap_exempt_earnings: false + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false benefit_cap_reduction: 3_247 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap_by_scheme.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap_by_scheme.yaml new file mode 100644 index 000000000..0f37b40de --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap_by_scheme.yaml @@ -0,0 +1,441 @@ +# Each scheme's own benefit cap exceptions and reduction. +# +# Universal Credit (UC Regs 2013): reg 79(1) "Unless regulation 82 or 83 +# applies, the benefit cap applies"; reg 82 earnings; reg 83(1) "a claimant" +# receiving listed benefits, or the LCWRA or carer element in the award; no +# exception for age; reg 81 reduction "the excess; minus any amount included +# in the award for the childcare costs element". +# Housing Benefit (HB Regs 2006): reg 75A "Unless regulation 75E or 75F +# applies"; reg 75E(2) "the claimant is, or the claimant and the claimant's +# partner are jointly, entitled to working tax credit"; reg 75F(1) the +# claimant or partner receiving listed benefits; reg 75D(2) the reduction +# leaves the reg 75 minimum (50 pence a week); reg 5 the HB Regs 2006, and so +# the cap, do not apply where the claimant or partner has reached the +# qualifying age for state pension credit unless on UC, IS, income-based JSA +# or income-related ESA. +# UC reg 80(2A) counts Housing Benefit as nil, and HB reg 75F(1)(g) lifts the +# Housing Benefit cap for a claimant receiving Universal Credit. +# Annual limits (UC reg 80A(2), HB reg 75CA(2)): single 14,753 / family +# 22,020 outside London. + +- name: UC reg 83(1)(a) - the LCWRA element lifts the UC cap; HB has no LCWRA exception + period: 2025 + input: + age: 30 + region: NORTH_EAST + uc_limited_capability_for_WRA: true + esa_income: 0 + esa_contrib: 0 + working_tax_credit: 0 + output: + is_uc_benefit_cap_exempt_specified_benefit: true + is_uc_benefit_cap_exempt: true + # HB reg 75F(1)(a) needs an employment and support allowance with the + # support component; limited capability alone is not on the list. + is_housing_benefit_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt: false + housing_benefit_benefit_cap: 14_753 + +- name: UC reg 83(1)(j) - the carer element lifts the UC cap; HB needs carer's allowance + period: 2025 + input: + age: 30 + care_hours: 35 + # No entitlement to carer's allowance or carer support payment. + carers_allowance_pre_overlap: 0 + carer_support_payment_pre_overlap: 0 + carers_allowance: 0 + carer_support_payment: 0 + working_tax_credit: 0 + output: + is_entitled_to_carer_benefit: false + is_uc_benefit_cap_exempt_specified_benefit: true + is_uc_benefit_cap_exempt: true + # HB reg 75F(1)(h)-(ha): entitlement to carer's allowance or carer + # support payment, which this carer does not have. + is_housing_benefit_benefit_cap_exempt: false + +- name: UC reg 83(1)(i) and HB reg 75F(1)(h) - carer's allowance lifts both caps + period: 2025 + input: + age: 30 + care_hours: 35 + carers_allowance: 4_000 + working_tax_credit: 0 + output: + is_uc_benefit_cap_exempt: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt: true + +- name: UC reg 83(1)(c) with reg 2, and HB reg 75F(1)(ea) - armed forces independence payment lifts both caps + period: 2025 + input: + age: 30 + region: NORTH_EAST + armed_forces_independence_payment: 6_000 + working_tax_credit: 0 + output: + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true + is_uc_benefit_cap_exempt: true + is_housing_benefit_benefit_cap_exempt: true + +- name: UC reg 82(1)(a) - earnings lift the UC cap; HB has no earnings exception + period: 2025 + input: + age: 30 + region: NORTH_EAST + employment_income: 15_000 + working_tax_credit: 0 + output: + # 2025-26: income tax 20% x (15,000 - 12,570) = 486; Class 1 NI 8% x + # (15,000 - 12,570) = 194.40; earned income 14,319.60, above the model's + # 10,152 a year (846 a month). + is_uc_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt: true + is_housing_benefit_benefit_cap_exempt: false + housing_benefit_benefit_cap: 14_753 + +- name: HB reg 75E(2) - working tax credit lifts the HB cap; it is no UC exception + period: 2025 + input: + age: 30 + region: NORTH_EAST + working_tax_credit: 1_000 + output: + is_housing_benefit_benefit_cap_exempt_working_tax_credit: true + is_housing_benefit_benefit_cap_exempt: true + is_uc_benefit_cap_exempt: false + uc_benefit_cap: 14_753 + +- name: HB reg 5 - a pension-age claimant's HB is under the pension-age regulations, which have no cap + period: 2025 + input: + age: 70 + working_tax_credit: 0 + output: + housing_benefit_pension_age_regulations_apply: true + is_housing_benefit_benefit_cap_exempt: true + # UC regs 79, 82 and 83 have no age exception. A claimant over the + # qualifying age can claim UC only on a qualifying claim (SI 2014/1230 + # reg 60A), for which reg 60C disapplies the cap. + is_uc_benefit_cap_exempt_earnings: false + is_uc_benefit_cap_exempt_specified_benefit: false + is_uc_benefit_cap_exempt_qualifying_age: true + is_uc_benefit_cap_exempt: true + +- name: SI 2014/1230 reg 60C - joint claimants who have both reached the qualifying age are not capped + # Reg 60C: "Where a qualifying claim is made by a single claimant who has, + # or joint claimants both of whom have, reached the qualifying age for state + # pension credit, regulation 79 ... is not to apply". Reg 60A makes such a + # claim possible for tax credit migrants; the model's own eligibility test + # does not, so the award is an input. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 72 + younger: + age: 68 + benunits: + benunit: + members: [older, younger] + universal_credit_pre_benefit_cap: 30_000 + uc_deductions: 0 + households: + household: + members: [older, younger] + region: LONDON + output: + is_uc_benefit_cap_exempt_qualifying_age: true + is_uc_benefit_cap_exempt: true + uc_benefit_cap_reduction: 0 + universal_credit: 30_000 + +- name: SI 2014/1230 reg 60C with UC reg 3(3) - a single claimant at the qualifying age whose partner cannot be a joint claimant is not capped + # The partner (say, subject to immigration control: reg 3(3)(e)) is not a + # claimant, so the claim is a single claimant's, and reg 60C asks only + # whether that claimant has reached the qualifying age. The award is an + # input, as above. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_claimant_or_partner: true + partner: + age: 50 + is_claimant_or_partner: true + uc_is_ineligible_partner: true + benunits: + benunit: + members: [claimant, partner] + universal_credit_pre_benefit_cap: 30_000 + uc_deductions: 0 + households: + household: + members: [claimant, partner] + region: LONDON + output: + is_uc_single_or_joint_claimant: [true, false] + is_uc_benefit_cap_exempt_qualifying_age: true + is_uc_benefit_cap_exempt: true + uc_benefit_cap_reduction: 0 + universal_credit: 30_000 + +- name: SI 2014/1230 reg 60C - the same award to joint claimants of whom only one has reached the qualifying age is capped + # Reg 60C needs both joint claimants to have reached the qualifying age. + # London couple cap 25,323 (reg 80A(2)(b)(i)); excess 30,000 - 25,323 = + # 4,677; no childcare costs element (reg 81). + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_claimant_or_partner: true + partner: + age: 50 + is_claimant_or_partner: true + benunits: + benunit: + members: [claimant, partner] + universal_credit_pre_benefit_cap: 30_000 + uc_deductions: 0 + households: + household: + members: [claimant, partner] + region: LONDON + output: + is_uc_single_or_joint_claimant: [true, true] + is_uc_benefit_cap_exempt_qualifying_age: false + is_uc_benefit_cap_exempt: false + uc_benefit_cap: 25_323 + uc_benefit_cap_reduction: 4_677 + universal_credit: 25_323 + +- name: UC regs 79-83 - a mixed-age couple on UC is capped; under reg 5(1)(b) so is their HB + period: 2025 + input: + people: + older: + age: 70 + is_claimant_or_partner: true + younger: + age: 40 + is_claimant_or_partner: true + benunits: + benunit: + members: [older, younger] + would_claim_uc: true + working_tax_credit: 0 + households: + household: + members: [older, younger] + region: NORTH_EAST + tenure_type: RENT_PRIVATELY + rent: 9_000 + output: + is_uc_entitled: true + is_uc_benefit_cap_exempt: false + uc_benefit_cap: 22_020 + # The younger member is on UC, so the HB Regs 2006 apply (reg 5(1)(b)). + housing_benefit_pension_age_regulations_apply: false + is_housing_benefit_benefit_cap_exempt: false + +- name: HB reg 5(3) - a mixed-age couple not on UC or an income-related benefit claims under the pension-age regulations + period: 2025 + input: + people: + older: + age: 70 + is_claimant_or_partner: true + younger: + age: 40 + is_claimant_or_partner: true + benunits: + benunit: + members: [older, younger] + would_claim_uc: false + working_tax_credit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + households: + household: + members: [older, younger] + region: NORTH_EAST + output: + housing_benefit_pension_age_regulations_apply: true + is_housing_benefit_benefit_cap_exempt: true + +- name: HB reg 5(1)(b) - a pension-age claimant whose partner is on income-based JSA claims under the working-age regulations + period: 2025 + input: + people: + older: + age: 70 + is_claimant_or_partner: true + younger: + age: 50 + is_claimant_or_partner: true + benunits: + benunit: + members: [older, younger] + would_claim_uc: false + working_tax_credit: 0 + income_support: 0 + jsa_income: 1_000 + esa_income: 0 + households: + household: + members: [older, younger] + region: NORTH_EAST + output: + housing_benefit_pension_age_regulations_apply: false + # Income-based JSA is not a reg 75F exception. + is_housing_benefit_benefit_cap_exempt: false + housing_benefit_benefit_cap: 22_020 + +- name: UC reg 81(1) - the reduction is the excess less the childcare costs element + period: 2025 + input: + benefit_cap_welfare_benefits: 30_000 + uc_benefit_cap: 22_020 + uc_childcare_element: 3_000 + universal_credit_pre_benefit_cap: 15_000 + housing_benefit_pre_benefit_cap: 0 + output: + # Excess 30,000 - 22,020 = 7,980; less childcare 3,000 = 4,980. + uc_benefit_cap_reduction: 4_980 + housing_benefit_benefit_cap_reduction: 0 + benefit_cap_reduction: 4_980 + +- name: UC reg 81(2) - no reduction where the childcare costs element exceeds the excess + period: 2025 + input: + benefit_cap_welfare_benefits: 30_000 + uc_benefit_cap: 22_020 + uc_childcare_element: 9_000 + universal_credit_pre_benefit_cap: 15_000 + output: + # Excess 7,980 < childcare 9,000. + uc_benefit_cap_reduction: 0 + +- name: UC reg 78(1) - the cap reduces an award of UC, so no award means no reduction + period: 2025 + input: + benefit_cap_welfare_benefits: 30_000 + uc_benefit_cap: 22_020 + uc_childcare_element: 0 + universal_credit_pre_benefit_cap: 0 + housing_benefit_pre_benefit_cap: 0 + output: + uc_benefit_cap_reduction: 0 + benefit_cap_reduction: 0 + +- name: HB reg 75D(1) - Housing Benefit is reduced by the excess + period: 2025 + input: + benefit_cap_welfare_benefits: 30_000 + housing_benefit_benefit_cap: 22_020 + housing_benefit_pre_benefit_cap: 10_000 + would_claim_housing_benefit: true + universal_credit_pre_benefit_cap: 0 + output: + # Excess 7,980 is below 10,000 - 0.50 x 52 = 9,974. + housing_benefit_benefit_cap_reduction: 7_980 + housing_benefit: 2_020 + uc_benefit_cap_reduction: 0 + benefit_cap_reduction: 7_980 + +- name: HB reg 75D(2) - the reduction leaves the reg 75 minimum of 50 pence a week + period: 2025 + input: + benefit_cap_welfare_benefits: 30_000 + housing_benefit_benefit_cap: 22_020 + housing_benefit_pre_benefit_cap: 5_000 + would_claim_housing_benefit: true + output: + # min(7,980, 5,000 - 26) = 4,974, leaving 0.50 x 52 = 26. + housing_benefit_benefit_cap_reduction: 4_974 + housing_benefit: 26 + +- name: HB reg 75D(2) - Housing Benefit already below 50 pence a week is not reduced + period: 2025 + input: + benefit_cap_welfare_benefits: 30_000 + housing_benefit_benefit_cap: 22_020 + housing_benefit_pre_benefit_cap: 20 + would_claim_housing_benefit: true + output: + # max(20 - 26, 0) = 0. Reg 75 itself (no award below 50 pence a week) is + # not modelled. + housing_benefit_benefit_cap_reduction: 0 + housing_benefit: 20 + +# A family paid Housing Benefit alongside Universal Credit (specified or +# temporary accommodation). The model's Housing Benefit eligibility rules this +# out for a Universal Credit claimant, so both awards are supplied. +- name: UC reg 80(2A) and HB reg 75F(1)(g) - a family receiving both is capped only through UC, with its Housing Benefit counted as nil + period: 2025 + input: + benefit_cap_welfare_benefits: 40_000 + uc_benefit_cap: 22_020 + uc_childcare_element: 0 + universal_credit_pre_benefit_cap: 15_000 + housing_benefit_pre_benefit_cap: 10_000 + would_claim_housing_benefit: true + output: + # UC: 40,000 - 10,000 Housing Benefit = 30,000; excess over 22,020 is + # 7,980. + uc_benefit_cap_reduction: 7_980 + # HB reg 75F(1)(g): the claimant is receiving Universal Credit. + is_housing_benefit_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt: true + housing_benefit_benefit_cap_reduction: 0 + housing_benefit: 10_000 + benefit_cap_reduction: 7_980 + +- name: UC reg 80(2A) - Housing Benefit alone cannot take a UC family over the cap + period: 2025 + input: + benefit_cap_welfare_benefits: 30_000 + uc_benefit_cap: 22_020 + uc_childcare_element: 0 + universal_credit_pre_benefit_cap: 15_000 + housing_benefit_pre_benefit_cap: 10_000 + would_claim_housing_benefit: true + output: + # 30,000 - 10,000 = 20,000, below 22,020. Counting the Housing Benefit + # would have given an excess of 7,980. + uc_benefit_cap_reduction: 0 + housing_benefit_benefit_cap_reduction: 0 + benefit_cap_reduction: 0 + +- name: Reg 80A(2)(a) and reg 75CA(2)(b) - in London a member of a couple claiming UC as a single person has the single UC cap and the family HB cap + period: 2025 + input: + people: + claimant: + age: 30 + is_claimant_or_partner: true + partner: + age: 30 + is_claimant_or_partner: true + uc_is_ineligible_partner: true + benunits: + benunit: + members: [claimant, partner] + is_uc_benefit_cap_exempt: false + is_housing_benefit_benefit_cap_exempt: false + households: + household: + members: [claimant, partner] + region: LONDON + output: + uc_member_of_couple_claims_as_single_person: true + uc_benefit_cap: 16_967 + housing_benefit_benefit_cap: 25_323 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml index 72cf06ab2..6467d450c 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_overlapping_benefits.yaml @@ -194,7 +194,8 @@ is_entitled_to_carer_benefit: true # HB Regs 2006 reg 75F(1)(h) and UC Regs 2013 reg 83(1)(i) exempt a # person "entitled to" Carer's Allowance. - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: Case 13, Severe Disablement Allowance at its maximum rate exceeds the allowance. period: 2026 @@ -255,7 +256,8 @@ carers_allowance_pre_overlap: 0 receives_carers_allowance: true is_entitled_to_carer_benefit: true - is_benefit_cap_exempt_health_disability: true + is_uc_benefit_cap_exempt_specified_benefit: true + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: Case 17, Pension Credit counts the allowance after the overlap. period: 2026 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/housing_benefit_on_passporting_benefit.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/housing_benefit_on_passporting_benefit.yaml index 655939c65..537f48df6 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/housing_benefit_on_passporting_benefit.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/housing_benefit_on_passporting_benefit.yaml @@ -56,7 +56,7 @@ input: would_claim_uc: true universal_credit_pre_benefit_cap: 2_000 - benefit_cap_reduction: 2_000 + uc_benefit_cap_reduction: 2_000 income_support: 0 jsa_income: 0 esa_income: 0 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/state_pension_credit_qualifying_age.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/state_pension_credit_qualifying_age.yaml index e993dfbfb..6dd710343 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/state_pension_credit_qualifying_age.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/state_pension_credit_qualifying_age.yaml @@ -39,7 +39,9 @@ is_pension_credit_eligible: true is_uc_eligible: false council_tax_reduction_pensioner: true - is_benefit_cap_exempt_other: true + # HB Regs 2006 reg 5: the pension-age regulations, which have no cap. + housing_benefit_pension_age_regulations_apply: true + is_housing_benefit_benefit_cap_exempt: true # SI 2000/729 reg 2(1)(i): 200 for a person under 80 not in residential care. winter_fuel_allowance: 200 @@ -161,7 +163,9 @@ is_pension_credit_eligible: false is_uc_eligible: true council_tax_reduction_pensioner: false - is_benefit_cap_exempt_other: false + # HB Regs 2006 reg 5: the pension-age regulations, which have no cap. + housing_benefit_pension_age_regulations_apply: false + is_housing_benefit_benefit_cap_exempt: false winter_fuel_allowance: 0 - name: Case 7, the same man is over both ages in 2018-19. diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_trading_loss.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_trading_loss.yaml index a48790bd0..9bb761603 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_trading_loss.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/universal_credit/uc_trading_loss.yaml @@ -76,7 +76,7 @@ uc_trading_losses_set_against_profits: [5_000, 0, 0, 0, 0] # £7,000 of earnings, below the reg 82 threshold, so the cap applies, # exactly as for £7,000 of profits and no loss. - is_benefit_cap_exempt_earnings: false + is_uc_benefit_cap_exempt_earnings: false universal_credit: 21_124 # Reg. 77(3)(b) sends the owner's company trade to reg. 57; reg. 57(2) @@ -105,7 +105,7 @@ uc_individual_earned_income_before_mif: 7_000 uc_individual_earned_income: 20_000 # Actual £7,000 is below £10,152, even though the MIF is £20,000. - is_benefit_cap_exempt_earnings: false + is_uc_benefit_cap_exempt_earnings: false - name: Company earnings without the loss exceed the cap threshold with the same deductions period: 2026 @@ -126,7 +126,7 @@ uc_trading_losses_set_against_profits: 0 uc_individual_earned_income_before_mif: 12_000 uc_individual_earned_income: 20_000 - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true - name: A company loss and sole trader loss never reduce employed earnings period: 2026 @@ -146,7 +146,7 @@ uc_company_self_employed_earnings: -10_000 uc_trading_losses_set_against_profits: 0 uc_individual_earned_income_before_mif: 12_000 - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true - name: Tax and NI on trades cancelled by losses never reduce employed earnings period: 2026 @@ -168,4 +168,4 @@ output: uc_trading_losses_set_against_profits: 1_000 uc_individual_earned_income_before_mif: 11_500 - is_benefit_cap_exempt_earnings: true + is_uc_benefit_cap_exempt_earnings: true diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml index e791a4029..f19bc2277 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner.yaml @@ -240,7 +240,9 @@ is_uc_eligible: false is_uc_entitled: false housing_benefit_pension_age_regulations_apply: true - is_benefit_cap_exempt: true + # Housing Benefit under the pension-age regulations has no cap; there is + # no Universal Credit award to cap. + is_housing_benefit_benefit_cap_exempt: true benefit_cap_reduction: 0 council_tax_reduction_pensioner: true council_tax_reduction_scheme_supported: true diff --git a/policyengine_uk/tests/test_abolish_benefit_cap_scenario.py b/policyengine_uk/tests/test_abolish_benefit_cap_scenario.py index f3afea782..8546e4ba1 100644 --- a/policyengine_uk/tests/test_abolish_benefit_cap_scenario.py +++ b/policyengine_uk/tests/test_abolish_benefit_cap_scenario.py @@ -32,7 +32,7 @@ def test_removes_benefit_cap_reduction(self): baseline_reduction = baseline.calculate("benefit_cap_reduction", 2026) baseline_uc = baseline.calculate("universal_credit", 2026) - reformed_cap = reformed.calculate("benefit_cap", 2026) + reformed_cap = reformed.calculate("uc_benefit_cap", 2026) reformed_reduction = reformed.calculate("benefit_cap_reduction", 2026) reformed_uc = reformed.calculate("universal_credit", 2026) reformed_uc_pre_cap = reformed.calculate( @@ -40,8 +40,11 @@ def test_removes_benefit_cap_reduction(self): ) assert baseline_reduction[0] > 0 - assert np.isfinite(baseline.calculate("benefit_cap", 2026)[0]) + assert np.isfinite(baseline.calculate("uc_benefit_cap", 2026)[0]) assert np.isinf(reformed_cap[0]) + # The scenario lifts the Housing Benefit cap too: both schemes read + # the same annual limits. + assert np.isinf(reformed.calculate("housing_benefit_benefit_cap", 2026)[0]) assert reformed_reduction.tolist() == [0.0] assert reformed_uc.tolist() == reformed_uc_pre_cap.tolist() assert reformed_uc[0] > baseline_uc[0] diff --git a/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py b/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py index 2e997d22c..682e1fa93 100644 --- a/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py +++ b/policyengine_uk/tests/test_benefit_cap_and_ctr_pension_age_properties.py @@ -32,15 +32,18 @@ Pension age and the family is neither on Universal Credit nor on a legacy income-related benefit. P2 Every Universal Credit award is capped unless a non-age exception applies: - such a family has a finite benefit cap. + such a family has a finite Universal Credit benefit cap. P3 Differential against the formulas before this change. A family with no member over State Pension age, or one over it with neither Universal - Credit nor a legacy income-related benefit, gets exactly the same benefit - cap, Universal Credit, Housing Benefit, Council Tax Reduction and household - net income. -P4 The change only removes exemptions: exempt now implies exempt before, the - cap is never higher, and Universal Credit and Housing Benefit never rise. - A CTR pensioner now was a CTR pensioner before. + Credit nor a legacy income-related benefit, gets exactly the same Housing + Benefit cap, Universal Credit, Housing Benefit, Council Tax Reduction and + household net income. A family with no member over State Pension age also + gets the same Universal Credit cap; one with such a member and no + Universal Credit award has no award for that cap to reduce. +P4 The change only removes exemptions: for each scheme, exempt now implies + exempt before and the cap is never higher, and Universal Credit and + Housing Benefit never rise. A CTR pensioner now was a CTR pensioner + before. P5 The Welsh and Scottish CTR schemes do not depend on pensioner status, but they count Universal Credit as income. So with the CTR pensioner test held as it is now (it also sets the pension-age applicable amount and @@ -93,19 +96,42 @@ TENURES = ["RENT_FROM_COUNCIL", "RENT_PRIVATELY", "OWNED_OUTRIGHT"] -# The two formulas as they were before this change, kept as the reference for -# the differential invariants. -class is_benefit_cap_exempt_other(Variable): +# The formulas as they were before this change, kept as the reference for +# the differential invariants. Before it, any member over State Pension age +# lifted the cap. Each scheme's cap exceptions now have their own variable, +# so the reference adds that age test to each scheme's non-age exceptions. +def has_pensioner(benunit, period): + return benunit.any(benunit.members("is_SP_age", period)) + + +class is_uc_benefit_cap_exempt(Variable): value_type = bool entity = BenUnit - label = "Benefit cap exemption before this change" + label = "Universal Credit benefit cap exemption before this change" definition_period = YEAR def formula(benunit, period, parameters): - has_pensioner = benunit.any(benunit.members("is_SP_age", period)) - afcs = benunit("afcs", period) > 0 - esa_support_component = benunit("esa_contrib", period) > 0 - return has_pensioner | afcs | esa_support_component + return ( + has_pensioner(benunit, period) + | benunit("is_uc_benefit_cap_exempt_earnings", period) + | benunit("is_uc_benefit_cap_exempt_specified_benefit", period) + ) + + +class is_housing_benefit_benefit_cap_exempt(Variable): + value_type = bool + entity = BenUnit + label = "Housing Benefit benefit cap exemption before this change" + definition_period = YEAR + + def formula(benunit, period, parameters): + return ( + has_pensioner(benunit, period) + | benunit( + "is_housing_benefit_benefit_cap_exempt_working_tax_credit", period + ) + | benunit("is_housing_benefit_benefit_cap_exempt_specified_benefit", period) + ) class council_tax_reduction_pensioner(Variable): @@ -125,7 +151,8 @@ def formula(benunit, period, parameters): class before_this_change(Reform): def apply(self): - self.update_variable(is_benefit_cap_exempt_other) + self.update_variable(is_uc_benefit_cap_exempt) + self.update_variable(is_housing_benefit_benefit_cap_exempt) self.update_variable(council_tax_reduction_pensioner) @@ -135,7 +162,8 @@ def apply(self): # through Universal Credit. class before_the_cap_change(Reform): def apply(self): - self.update_variable(is_benefit_cap_exempt_other) + self.update_variable(is_uc_benefit_cap_exempt) + self.update_variable(is_housing_benefit_benefit_cap_exempt) @st.composite @@ -218,11 +246,12 @@ def situation(units): "is_uc_entitled", "housing_benefit_pension_age_regulations_apply", "council_tax_reduction_pensioner", - "is_benefit_cap_exempt", - "is_benefit_cap_exempt_health_disability", - "is_benefit_cap_exempt_earnings", - "is_benefit_cap_exempt_other", - "benefit_cap", + "is_uc_benefit_cap_exempt", + "is_uc_benefit_cap_exempt_specified_benefit", + "is_uc_benefit_cap_exempt_earnings", + "uc_benefit_cap", + "is_housing_benefit_benefit_cap_exempt", + "housing_benefit_benefit_cap", "universal_credit", "housing_benefit", "council_tax_benefit", @@ -231,8 +260,10 @@ def situation(units): ] HOUSEHOLD = ["household_net_income"] BEFORE = [ - "is_benefit_cap_exempt", - "benefit_cap", + "is_uc_benefit_cap_exempt", + "uc_benefit_cap", + "is_housing_benefit_benefit_cap_exempt", + "housing_benefit_benefit_cap", "universal_credit", "housing_benefit", "council_tax_benefit", @@ -280,22 +311,33 @@ def test_benefit_cap_and_ctr_pension_age_invariants(units): assert now["council_tax_reduction_pensioner"][i] == expected, unit # P2 other_exception = ( - now["is_benefit_cap_exempt_health_disability"][i] - or now["is_benefit_cap_exempt_earnings"][i] + now["is_uc_benefit_cap_exempt_specified_benefit"][i] + or now["is_uc_benefit_cap_exempt_earnings"][i] or now["afcs"][i] > 0 or now["esa_contrib"][i] > 0 ) if on_uc and not other_exception: - assert not now["is_benefit_cap_exempt"][i], unit - assert np.isfinite(now["benefit_cap"][i]), unit + assert not now["is_uc_benefit_cap_exempt"][i], unit + assert np.isfinite(now["uc_benefit_cap"][i]), unit # P3 if not has_pension_age_claimant(unit) or not (on_uc or legacy): + uc_cap = ["is_uc_benefit_cap_exempt", "uc_benefit_cap"] for name in BEFORE: + if name in uc_cap and has_pension_age_claimant(unit): + # Before this change the age test lifted the Universal + # Credit cap too, but such a family has no award for it + # to reduce. + assert close(now["universal_credit"][i], 0), unit + continue assert close(now[name][i], before[name][i]), (name, unit) # P4 - if now["is_benefit_cap_exempt"][i]: - assert before["is_benefit_cap_exempt"][i], unit - assert now["benefit_cap"][i] <= before["benefit_cap"][i], unit + for exempt, cap in [ + ("is_uc_benefit_cap_exempt", "uc_benefit_cap"), + ("is_housing_benefit_benefit_cap_exempt", "housing_benefit_benefit_cap"), + ]: + if now[exempt][i]: + assert before[exempt][i], (exempt, unit) + assert now[cap][i] <= before[cap][i], (cap, unit) assert now["universal_credit"][i] <= before["universal_credit"][i] + 0.01 assert now["housing_benefit"][i] <= before["housing_benefit"][i] + 0.01 if now["council_tax_reduction_pensioner"][i]: @@ -358,12 +400,15 @@ def run(older_is_pension_age): ) return { name: np.asarray(simulation.calculate(name, PERIOD)) - for name in ["is_benefit_cap_exempt", "benefit_cap_reduction"] + for name in ["is_uc_benefit_cap_exempt", "benefit_cap_reduction"] } working, mixed = run(False), run(True) for i, c in enumerate(couples): - assert mixed["is_benefit_cap_exempt"][i] == working["is_benefit_cap_exempt"][i] + assert ( + mixed["is_uc_benefit_cap_exempt"][i] + == working["is_uc_benefit_cap_exempt"][i] + ) assert close( mixed["benefit_cap_reduction"][i], working["benefit_cap_reduction"][i] ), c diff --git a/policyengine_uk/tests/test_benefit_cap_lha_scheme_split_properties.py b/policyengine_uk/tests/test_benefit_cap_lha_scheme_split_properties.py new file mode 100644 index 000000000..bc505ec5d --- /dev/null +++ b/policyengine_uk/tests/test_benefit_cap_lha_scheme_split_properties.py @@ -0,0 +1,905 @@ +"""Differential and property tests for splitting the benefit cap and the LHA +shared accommodation rate between Universal Credit and Housing Benefit. + +Until this change one set of variables served both schemes: the cap rate +(``is_benefit_cap_single_claimant_rate``), the exceptions +(``is_benefit_cap_exempt_health_disability``, ``_other``, ``_earnings``), +the cap and its reduction (``benefit_cap``, ``benefit_cap_reduction``) and +the shared accommodation test behind ``LHA_category``. A Universal Credit +claim by a member of a couple as a single person (UC Regs 2013 reg. 3(3)) +followed the Universal Credit rule unless the family claimed legacy +benefits. ``REFERENCE`` re-adds those formulas from the parent commit +under ``reference_`` names, so one simulation computes the old and the new +values for the same families. They are verbatim except for the names they +read, the inlined stopgap helpers and the earnings exception's dead code. +The reference awards read the current ``uc_deductions``, which these +single-household simulations leave at nil. + +Invariants, each over generated families and the explicit examples: + +1. Cap rate. Where the two schemes' rates agree, the old shared rate is + that rate. Where they differ, the old rate was the scheme the family + claimed under for a reg. 3(3) single claim (legacy: Housing Benefit; + otherwise Universal Credit), and otherwise the family rate whenever + either scheme gave it (the old test took responsibility under either). +2. Exceptions. With nobody at State Pension age and no armed forces + independence payment: where the schemes agree, the old exception is + theirs; with no reg. 3(3) single claim, the old exception is exactly the + union of the two schemes' (it listed both schemes' grounds). The age and + AFIP cases are intended changes (the UC cap has no age exception; HB + Regs 2006 reg. 5; AFIP lifts both caps under UC reg. 83(1)(c) with reg. 2 + and HB reg. 75F(1)(ea)) and are pinned in the YAML tests and examples. +3. LHA category. Without armed forces independence payment (added to the + exceptions of both schemes here), the old category is the new category + of the scheme the family claims under (legacy: Housing Benefit, + otherwise Universal Credit), and with no reg. 3(3) single claim the two + new categories are equal. Housing Benefit's comparisons need the two + schemes' size criteria to give the same number of bedrooms. The old + category read the Universal Credit count (``LHA_allowed_bedrooms``); + #2006 gave Housing Benefit its own (HB Regs 2006 reg. 13D(3), + ``housing_benefit_LHA_allowed_bedrooms``), which counts a young person + in the family whom the Universal Credit count leaves out, such as one + who is not a qualifying young person for Universal Credit. This change + leaves both counts as main has them. +4. Reductions. 0 <= each scheme's reduction <= the excess of the welfare + benefits over that scheme's cap; the UC reduction is the excess, with + Housing Benefit counted as nil (reg. 80(2A)), less the childcare costs + element (reg. 81); Housing Benefit after the cap keeps at least min(its + amount before the cap, 50 pence a week) (reg. 75D(2)), and a family + receiving Universal Credit has no Housing Benefit reduction (HB reg. + 75F(1)(g)); ``benefit_cap_reduction`` is the sum of the two. The model + gives no family both awards before the cap; the YAML tests and + ``test_benefit_cap_uc_and_hb_properties.py`` supply both. +5. Awards. Where a scheme's cap equals the old cap and neither reg. 81's + childcare offset nor reg. 75D(2)'s minimum applies, its award equals the + award under the old shared cap. +6. The maximum rent (LHA) is the rent capped at the Housing Benefit LHA + rate, which equals the Universal Credit category's weekly rate wherever + the two categories agree. +""" + +import copy + +import numpy as np +from hypothesis import HealthCheck, example, given, settings +from hypothesis import strategies as st + +from policyengine_core.reforms import Reform +from policyengine_uk import Simulation +from policyengine_uk.model_api import * +from policyengine_uk.utils.benefit_unit import add_for_members +from policyengine_uk.variables.gov.dwp.LHA_category import LHACategory + +# model_api's YEAR is the period type the reference variables use. +PERIOD = 2026 +PROPERTY_SETTINGS = settings( + max_examples=12, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +# --------------------------------------------------------------------------- +# The shared rules before the split (parent commit 17b9b8a16), verbatim but +# for the names and the inlined stopgap helpers. + + +def _single_claim_in_shared_rules(benunit, period): + return benunit("uc_member_of_couple_claims_as_single_person", period) & ~benunit( + "claims_legacy_benefits", period + ) + + +def _other_member_in_shared_rules(person, period): + return ( + person("uc_is_ineligible_partner", period) + & person.benunit("uc_member_of_couple_claims_as_single_person", period) + & ~person.benunit("claims_legacy_benefits", period) + ) + + +class reference_is_benefit_cap_single_claimant_rate(Variable): + value_type = bool + entity = BenUnit + label = "Old shared benefit cap single claimant rate" + definition_period = YEAR + + def formula(benunit, period, parameters): + single_claimant = ~benunit("is_couple", period) | _single_claim_in_shared_rules( + benunit, period + ) + return single_claimant & ~benunit( + "is_responsible_for_child_or_young_person_for_uc_or_housing_benefit", + period, + ) + + +class reference_is_benefit_cap_exempt_health_disability(Variable): + value_type = bool + entity = BenUnit + label = "Old shared health and disability exception" + definition_period = YEAR + + def formula(benunit, period, parameters): + person = benunit.members + not_a_claimant = _other_member_in_shared_rules(person, period) + has_lcwra = benunit.any( + person("uc_limited_capability_for_WRA", period) & ~not_a_claimant + ) + gets_uc_carer_element = benunit("uc_carer_element", period) > 0 + qualifying_personal_benefits = add_for_members( + benunit, + period, + [ + "attendance_allowance", + "carers_allowance", + "carer_support_payment", + "dla", + "pip_dl", + "pip_m", + "iidb", + ], + ~not_a_claimant, + ) + qualifying_benunit_benefits = add( + benunit, period, ["esa_income", "working_tax_credit"] + ) + afcs = add_for_members(benunit, period, ["afcs"], ~not_a_claimant) > 0 + esa_support_component = ( + add_for_members(benunit, period, ["esa_contrib"], ~not_a_claimant) > 0 + ) + return ( + has_lcwra + | gets_uc_carer_element + | (qualifying_personal_benefits > 0) + | (qualifying_benunit_benefits > 0) + | afcs + | esa_support_component + ) + + +class reference_is_benefit_cap_exempt_other(Variable): + value_type = bool + entity = BenUnit + label = "Old shared other exception" + definition_period = YEAR + + def formula(benunit, period, parameters): + person = benunit.members + has_pensioner = benunit.any(person("is_SP_age", period)) + not_a_claimant = _other_member_in_shared_rules(person, period) + afcs = add_for_members(benunit, period, ["afcs"], ~not_a_claimant) > 0 + esa_support_component = ( + add_for_members(benunit, period, ["esa_contrib"], ~not_a_claimant) > 0 + ) + return has_pensioner | afcs | esa_support_component + + +class reference_is_benefit_cap_exempt_earnings(Variable): + value_type = bool + entity = BenUnit + label = "Old shared earnings exception" + definition_period = YEAR + + def formula(benunit, period, parameters): + uc_earned = benunit.sum( + benunit.members("employment_income", period) + + benunit.members("self_employment_income", period) + - benunit.members("income_tax", period) + - benunit.members("national_insurance", period) + ) + return uc_earned >= 10_152 + + +class reference_is_benefit_cap_exempt(Variable): + value_type = bool + entity = BenUnit + label = "Old shared benefit cap exemption" + definition_period = YEAR + + def formula(benunit, period, parameters): + return ( + benunit("reference_is_benefit_cap_exempt_health_disability", period) + | benunit("reference_is_benefit_cap_exempt_earnings", period) + | benunit("reference_is_benefit_cap_exempt_other", period) + ) + + +class reference_benefit_cap(Variable): + value_type = float + entity = BenUnit + label = "Old shared benefit cap" + definition_period = YEAR + + def formula(benunit, period, parameters): + single_claimant = benunit( + "reference_is_benefit_cap_single_claimant_rate", period + ) + household_region = benunit.members.household("region", period) + region = benunit.value_from_first_person(household_region) + in_london = region == household_region.possible_values.LONDON + cap = parameters(period).gov.dwp.benefit_cap + rate = select( + [ + single_claimant & in_london, + single_claimant & ~in_london, + ~single_claimant & in_london, + ~single_claimant & ~in_london, + ], + [ + cap.single.in_london, + cap.single.outside_london, + cap.non_single.in_london, + cap.non_single.outside_london, + ], + ) + exempt = benunit("reference_is_benefit_cap_exempt", period) + return where(exempt, np.inf, rate) + + +class reference_benefit_cap_reduction(Variable): + value_type = float + entity = BenUnit + label = "Old shared benefit cap reduction" + definition_period = YEAR + + def formula(benunit, period, parameters): + capped = add( + benunit, + period, + [ + "child_benefit", + "child_tax_credit", + "jsa_income", + "income_support", + "esa_income", + "universal_credit_pre_benefit_cap", + "housing_benefit_pre_benefit_cap", + "jsa_contrib", + "incapacity_benefit", + "esa_contrib", + "sda", + ], + ) + return max_(capped - benunit("reference_benefit_cap", period), 0) + + +class reference_is_lha_shared_accommodation_rate_specified_renter(Variable): + value_type = bool + entity = BenUnit + label = "Old shared specified renter test" + definition_period = YEAR + + def formula(benunit, period, parameters): + p = parameters(period).gov.dwp.LHA + person = benunit.members + claims_as_single_person = _single_claim_in_shared_rules(benunit, period) + other_member = _other_member_in_shared_rules(person, period) + renter = person("is_claimant_or_partner", period) & ~other_member + renter_age = benunit.max(where(renter, person("age", period), -np.inf)) + # The list before this change, without armed forces independence + # payment. + excepted_disabled_renter = benunit.any( + renter + & ( + add( + person, + period, + ["attendance_allowance", "dla_sc_middle_plus", "pip_dl"], + ) + > 0 + ) + ) + return ( + (~benunit("is_couple", period) | claims_as_single_person) + & (renter_age < p.shared_accommodation_age_threshold) + & ~benunit( + "is_responsible_for_child_or_young_person_for_uc_or_housing_benefit", + period, + ) + & ~benunit("lha_renter_has_non_dependant", period) + & ~excepted_disabled_renter + ) + + +class reference_LHA_category(Variable): + value_type = Enum + entity = BenUnit + label = "Old shared LHA category" + definition_period = YEAR + possible_values = LHACategory + default_value = LHACategory.C + + def formula(benunit, period, parameters): + num_rooms = benunit("LHA_allowed_bedrooms", period.this_year) + household = benunit.members.household + is_shared = benunit.any(household("is_shared_accommodation", period.this_year)) + can_only_claim_shared = benunit( + "reference_is_lha_shared_accommodation_rate_specified_renter", period + ) + return select( + [ + is_shared | can_only_claim_shared, + num_rooms == 1, + num_rooms == 2, + num_rooms == 3, + num_rooms > 3, + ], + [ + LHACategory.A, + LHACategory.B, + LHACategory.C, + LHACategory.D, + LHACategory.E, + ], + ) + + +class reference_universal_credit(Variable): + value_type = float + entity = BenUnit + label = "Universal Credit under the old shared cap" + definition_period = YEAR + + def formula(benunit, period, parameters): + # The parent's formula, reading the old shared reduction. Deductions + # are the current uc_deductions, which read the UC reduction; the + # single-household simulations here have none (uc_has_deduction). + uc_max_entitlement = benunit("universal_credit_pre_benefit_cap", period) + benefit_cap_reduction = benunit("reference_benefit_cap_reduction", period) + deductions = benunit("uc_deductions", period) + 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) + + +class reference_housing_benefit(Variable): + value_type = float + entity = BenUnit + label = "Housing Benefit under the old shared cap" + definition_period = YEAR + + def formula(benunit, period, parameters): + return max_( + 0, + benunit("housing_benefit_pre_benefit_cap", period) + - benunit("reference_benefit_cap_reduction", period), + ) + + +REFERENCE_VARIABLES = [ + reference_is_benefit_cap_single_claimant_rate, + reference_is_benefit_cap_exempt_health_disability, + reference_is_benefit_cap_exempt_other, + reference_is_benefit_cap_exempt_earnings, + reference_is_benefit_cap_exempt, + reference_benefit_cap, + reference_benefit_cap_reduction, + reference_is_lha_shared_accommodation_rate_specified_renter, + reference_LHA_category, + reference_universal_credit, + reference_housing_benefit, +] + + +class REFERENCE(Reform): + def apply(self): + for variable in REFERENCE_VARIABLES: + self.add_variable(variable) + + +# --------------------------------------------------------------------------- +# Families. + +REGIONS = ["LONDON", "NORTH_EAST", "WALES", "SCOTLAND"] +rarely = st.integers(0, 5).map(lambda n: n == 0) +sometimes_paid = st.one_of(*[st.just(0.0)] * 4, st.floats(500, 6_000)) +earnings = st.one_of(st.just(0.0), st.just(0.0), st.floats(0, 40_000)) + + +@st.composite +def adults(draw, ages): + return dict( + age=draw(ages), + employment_income=draw(earnings), + uc_limited_capability_for_WRA=draw(rarely), + care_hours=draw(st.sampled_from([0, 0, 0, 0, 35])), + attendance_allowance=draw(sometimes_paid), + pip_dl=draw(sometimes_paid), + carers_allowance=draw(sometimes_paid), + afcs=draw(sometimes_paid), + esa_contrib=draw(sometimes_paid), + armed_forces_independence_payment=draw( + st.one_of(*[st.just(0.0)] * 6, st.just(6_000.0)) + ), + ) + + +@st.composite +def families(draw): + has_partner = draw(st.booleans()) + return dict( + claimant=draw(adults(st.integers(18, 75))), + partner=draw(adults(st.integers(16, 80))) if has_partner else None, + partner_flag=draw(st.sampled_from([None, None, True, False])), + children=[ + dict( + age=draw(st.integers(0, 19)), + in_education=draw(st.booleans()), + ) + for _ in range(draw(st.integers(0, 3))) + ], + legacy=draw(st.booleans()), + working_tax_credit=draw(st.one_of(*[st.just(0.0)] * 4, st.just(1_500.0))), + childcare=draw(st.one_of(*[st.just(0.0)] * 3, st.floats(500, 8_000))), + rent=draw(st.floats(3_000, 30_000)), + region=draw(st.sampled_from(REGIONS)), + lodger=draw(rarely), + ) + + +populations = st.lists(families(), min_size=1, max_size=6) + + +def adult_inputs(age, **inputs): + values = dict( + age=age, + employment_income=0.0, + uc_limited_capability_for_WRA=False, + care_hours=0, + attendance_allowance=0.0, + pip_dl=0.0, + carers_allowance=0.0, + afcs=0.0, + esa_contrib=0.0, + armed_forces_independence_payment=0.0, + ) + values.update(inputs) + return values + + +def family(claimant, partner=None, **kw): + values = dict( + claimant=claimant, + partner=partner, + partner_flag=None, + children=[], + legacy=False, + working_tax_credit=0.0, + childcare=0.0, + rent=12_000.0, + region="NORTH_EAST", + lodger=False, + benunit_inputs={}, + ) + values.update(kw) + return values + + +def example_families(): + """Families in which each scheme difference occurs.""" + return [ + # A reg. 3(3) single claim, Universal Credit (partner 17, no reg. 8 + # circumstance): single for the UC cap and shared rate. + family(adult_inputs(24), adult_inputs(17), rent=8_000.0), + # The same couple claiming legacy benefits. + family(adult_inputs(24), adult_inputs(17), legacy=True, rent=8_000.0), + # A large family on UC above the cap, with childcare costs (reg. 81). + family( + adult_inputs(30, employment_income=3_000.0), + adult_inputs(30), + children=[dict(age=a, in_education=False) for a in (1, 3, 5)], + childcare=4_000.0, + rent=24_000.0, + region="LONDON", + ), + # A lone parent with an HB-only young person (19, started at 17). + family( + adult_inputs(30), + children=[dict(age=19, in_education=True)], + rent=9_000.0, + ), + # A single renter under 35 receiving AFIP. + family(adult_inputs(28, armed_forces_independence_payment=6_000.0)), + # A mixed-age couple on UC with no other ground. + family(adult_inputs(70), adult_inputs(40)), + # A single claimant with LCWRA continuing a Housing Benefit award. + family(adult_inputs(30, uc_limited_capability_for_WRA=True), legacy=True), + # A lone parent of five on Housing Benefit and income support whose + # excess is more than their Housing Benefit (reg. 75D(2)). + family( + adult_inputs(30), + children=[dict(age=a, in_education=False) for a in (1, 3, 5, 7, 9)], + legacy=True, + rent=20_000.0, + region="LONDON", + benunit_inputs=dict(income_support=24_000.0), + ), + ] + + +EXAMPLE_FAMILIES = example_families() + + +def situation(population, year=PERIOD): + people, benunits, households, lodger_units = {}, {}, {}, {} + for i, f in enumerate(population): + members = [] + adults_in = [("claimant", f["claimant"])] + if f["partner"] is not None: + adults_in.append(("partner", f["partner"])) + for role, inputs in adults_in: + name = f"{role}{i}" + person = {k: {year: v} for k, v in inputs.items()} + person["is_claimant_or_partner"] = {year: True} + person["would_claim_marriage_allowance"] = {year: False} + if role == "partner" and f["partner_flag"] is not None: + person["uc_is_ineligible_partner"] = {year: f["partner_flag"]} + people[name] = person + members.append(name) + for k, child in enumerate(f["children"]): + name = f"child{i}_{k}" + people[name] = { + "age": {year: child["age"]}, + "is_claimant_or_partner": {year: False}, + "current_education": { + year: "UPPER_SECONDARY" + if child["in_education"] + else "NOT_IN_EDUCATION" + }, + } + if child["age"] == 19 and child["in_education"]: + # Started at 17 and past UC's terminal date: a Child Benefit + # (HB) young person but not a UC qualifying young person. + people[name][ + "age_started_or_accepted_current_education_or_training" + ] = {year: 17} + people[name][ + "is_before_universal_credit_qualifying_young_person_terminal_date" + ] = {year: False} + members.append(name) + benunit = { + "members": list(members), + "would_claim_uc": {year: not f["legacy"]}, + "working_tax_credit": {year: f["working_tax_credit"]}, + } + if f["childcare"]: + benunit["uc_childcare_element"] = {year: f["childcare"]} + for variable, value in f.get("benunit_inputs", {}).items(): + benunit[variable] = {year: value} + benunits[f"b{i}"] = benunit + household_members = list(members) + if f["lodger"]: + name = f"lodger{i}" + people[name] = {"age": {year: 45}} + lodger_units[f"lodger_unit{i}"] = {"members": [name]} + household_members.append(name) + if f["legacy"]: + # A continuing Housing Benefit award (SI 2014/1230 reg. 8). + people[f"claimant{i}"]["housing_benefit_reported"] = {year: 3_000.0} + households[f"h{i}"] = { + "members": household_members, + "rent": {year: f["rent"]}, + "tenure_type": {year: "RENT_PRIVATELY"}, + "region": {year: f["region"]}, + } + benunits.update(lodger_units) + return {"people": people, "benunits": benunits, "households": households} + + +BENUNIT_VARIABLES = [ + "is_couple", + "claims_legacy_benefits", + "uc_member_of_couple_claims_as_single_person", + "is_uc_benefit_cap_single_claimant_rate", + "is_housing_benefit_benefit_cap_single_claimant_rate", + "reference_is_benefit_cap_single_claimant_rate", + "is_responsible_for_child_or_qualifying_young_person_for_universal_credit", + "is_responsible_for_child_or_young_person_for_legacy_benefits", + "is_uc_benefit_cap_exempt", + "is_housing_benefit_benefit_cap_exempt", + "reference_is_benefit_cap_exempt", + "uc_benefit_cap", + "housing_benefit_benefit_cap", + "reference_benefit_cap", + "benefit_cap_welfare_benefits", + "uc_benefit_cap_reduction", + "housing_benefit_benefit_cap_reduction", + "benefit_cap_reduction", + "reference_benefit_cap_reduction", + "uc_childcare_element", + "universal_credit_pre_benefit_cap", + "housing_benefit_pre_benefit_cap", + "universal_credit", + "housing_benefit", + "reference_universal_credit", + "reference_housing_benefit", + "benunit_rent", + "LHA_allowed_bedrooms", + "housing_benefit_LHA_allowed_bedrooms", + "LHA_cap", + "housing_benefit_LHA_rate", + "BRMA_LHA_rate", +] +CATEGORIES = ["LHA_category", "housing_benefit_LHA_category", "reference_LHA_category"] + + +def calculate(population): + sim = Simulation(situation=situation(population), reform=REFERENCE) + values = {v: np.asarray(sim.calculate(v, PERIOD)) for v in BENUNIT_VARIABLES} + for v in CATEGORIES: + values[v] = np.asarray(sim.calculate(v, PERIOD)).astype(str) + sp_age = np.asarray(sim.calculate("is_SP_age", PERIOD, map_to="benunit")) + values["anyone_sp_age"] = sp_age > 0 + afip = np.asarray( + sim.calculate("armed_forces_independence_payment", PERIOD, map_to="benunit") + ) + values["afip"] = afip > 0 + values["wtc_exempt"] = np.asarray( + sim.calculate( + "is_housing_benefit_benefit_cap_exempt_working_tax_credit", PERIOD + ) + ).astype(bool) + return values + + +# --------------------------------------------------------------------------- +# Properties. + + +@PROPERTY_SETTINGS +@given(population=populations) +@example(population=example_families()) +def test_cap_rate_is_the_old_rate_where_the_schemes_agree(population): + v = calculate(population) + uc, hb, ref = ( + v["is_uc_benefit_cap_single_claimant_rate"], + v["is_housing_benefit_benefit_cap_single_claimant_rate"], + v["reference_is_benefit_cap_single_claimant_rate"], + ) + agree = uc == hb + np.testing.assert_array_equal(ref[agree], uc[agree], err_msg=str(population)) + single_claim = v["uc_member_of_couple_claims_as_single_person"].astype(bool) + legacy = v["claims_legacy_benefits"].astype(bool) + same_responsibility = ( + v["is_responsible_for_child_or_qualifying_young_person_for_universal_credit"] + == v["is_responsible_for_child_or_young_person_for_legacy_benefits"] + ) + # A reg. 3(3) single claim with no responsibility difference: the old + # rate was the claimed scheme's. + stopgap = ~agree & single_claim & same_responsibility + np.testing.assert_array_equal( + ref[stopgap], np.where(legacy, hb, uc)[stopgap], err_msg=str(population) + ) + # A responsibility difference: the old test gave the family rate if + # either scheme did. + union = ~agree & ~single_claim + np.testing.assert_array_equal(ref[union], (uc & hb)[union], err_msg=str(population)) + # HB has no single claim by a member of a couple (HB Regs 2006 reg. 2(1)). + assert not np.any(hb[v["is_couple"].astype(bool)]), population + + +@PROPERTY_SETTINGS +@given(population=populations) +@example(population=example_families()) +def test_exceptions_are_the_old_exception_where_the_schemes_agree(population): + v = calculate(population) + uc, hb, ref = ( + v["is_uc_benefit_cap_exempt"].astype(bool), + v["is_housing_benefit_benefit_cap_exempt"].astype(bool), + v["reference_is_benefit_cap_exempt"].astype(bool), + ) + # Intended changes, pinned in YAML instead: the UC cap has no age + # exception and HB reg 5 sets the pension-age one; armed forces + # independence payment now lifts both caps (UC reg 83(1)(c) with reg 2; + # HB reg 75F(1)(ea)). + working_age = ~v["anyone_sp_age"] & ~v["afip"] + single_claim = v["uc_member_of_couple_claims_as_single_person"].astype(bool) + legacy = v["claims_legacy_benefits"].astype(bool) + # The old exception listed both schemes' grounds. + union = working_age & ~single_claim + np.testing.assert_array_equal(ref[union], (uc | hb)[union], err_msg=str(population)) + agree = union & (uc == hb) + np.testing.assert_array_equal(ref[agree], uc[agree], err_msg=str(population)) + # A reg. 3(3) single claim outside legacy benefits: the old exception + # left out the other member as UC does, and added working tax credit. + uc_single_claim = working_age & single_claim & ~legacy + np.testing.assert_array_equal( + ref[uc_single_claim], + (uc | v["wtc_exempt"])[uc_single_claim], + err_msg=str(population), + ) + + +@PROPERTY_SETTINGS +@given(population=populations) +@example(population=example_families()) +def test_lha_category_is_the_claimed_schemes_old_category(population): + v = calculate(population) + uc, hb, ref = ( + v["LHA_category"], + v["housing_benefit_LHA_category"], + v["reference_LHA_category"], + ) + legacy = v["claims_legacy_benefits"].astype(bool) + no_afip = ~v["afip"] + # Each scheme's size criteria (see invariant 3). + same_bedrooms = ( + v["LHA_allowed_bedrooms"] == v["housing_benefit_LHA_allowed_bedrooms"] + ) + claimed = np.where(legacy, hb, uc) + compared = no_afip & (~legacy | same_bedrooms) + np.testing.assert_array_equal( + ref[compared], claimed[compared], err_msg=str(population) + ) + no_single_claim = ~v["uc_member_of_couple_claims_as_single_person"].astype(bool) + compared = no_single_claim & same_bedrooms + np.testing.assert_array_equal(uc[compared], hb[compared], err_msg=str(population)) + + +@PROPERTY_SETTINGS +@given(population=populations) +@example(population=example_families()) +def test_each_scheme_reduces_by_its_own_excess(population): + v = calculate(population) + welfare = v["benefit_cap_welfare_benefits"] + on_uc = v["universal_credit_pre_benefit_cap"] > 0 + hb_pre = v["housing_benefit_pre_benefit_cap"] + # UC Regs 2013 reg. 80(2A): Housing Benefit counts as nil for UC. + uc_excess = np.maximum(welfare - hb_pre - v["uc_benefit_cap"], 0) + hb_excess = np.maximum(welfare - v["housing_benefit_benefit_cap"], 0) + uc_red, hb_red = ( + v["uc_benefit_cap_reduction"], + v["housing_benefit_benefit_cap_reduction"], + ) + # UC Regs 2013 reg. 81: the excess less the childcare costs element, on + # an award only. + np.testing.assert_allclose( + uc_red, + np.where(on_uc, np.maximum(uc_excess - v["uc_childcare_element"], 0), 0), + atol=0.01, + err_msg=str(population), + ) + assert np.all((0 <= hb_red) & (hb_red <= hb_excess + 0.01)), population + # HB Regs 2006 reg. 75D(2) with reg. 75: at least 50 pence a week stays. + minimum = 0.5 * 52 + assert np.all(v["housing_benefit"] >= np.minimum(hb_pre, minimum) - 0.01), ( + population + ) + assert np.all(v["housing_benefit"] <= hb_pre + 0.01), population + np.testing.assert_allclose( + v["benefit_cap_reduction"], uc_red + hb_red, atol=0.01, err_msg=str(population) + ) + # HB Regs 2006 reg. 75F(1)(g): no Housing Benefit cap for a claimant + # receiving Universal Credit. + assert np.all(hb_red[on_uc] == 0), population + + +@PROPERTY_SETTINGS +@given(population=populations) +@example(population=example_families()) +def test_awards_are_the_old_awards_where_the_caps_agree(population): + v = calculate(population) + old_cap = v["reference_benefit_cap"] + uc_same = ( + np.isclose(v["uc_benefit_cap"], old_cap, equal_nan=False) + | (np.isinf(v["uc_benefit_cap"]) & np.isinf(old_cap)) + ) & (v["uc_childcare_element"] == 0) + np.testing.assert_allclose( + v["universal_credit"][uc_same], + v["reference_universal_credit"][uc_same], + atol=0.01, + err_msg=str(population), + ) + hb_pre = v["housing_benefit_pre_benefit_cap"] + old_reduction = v["reference_benefit_cap_reduction"] + hb_same = ( + np.isclose(v["housing_benefit_benefit_cap"], old_cap) + | (np.isinf(v["housing_benefit_benefit_cap"]) & np.isinf(old_cap)) + ) & ((old_reduction == 0) | (hb_pre - old_reduction >= 0.5 * 52)) + np.testing.assert_allclose( + v["housing_benefit"][hb_same], + v["reference_housing_benefit"][hb_same], + atol=0.01, + err_msg=str(population), + ) + + +@PROPERTY_SETTINGS +@given(population=populations) +@example(population=example_families()) +def test_maximum_rent_reads_the_housing_benefit_category(population): + v = calculate(population) + np.testing.assert_allclose( + v["LHA_cap"], + np.minimum(v["benunit_rent"], v["housing_benefit_LHA_rate"]), + atol=0.01, + err_msg=str(population), + ) + same = v["LHA_category"] == v["housing_benefit_LHA_category"] + np.testing.assert_allclose( + v["housing_benefit_LHA_rate"][same], + v["BRMA_LHA_rate"][same], + atol=0.01, + err_msg=str(population), + ) + + +def test_examples_reach_the_cases(): + """Each example changes the result it is there for.""" + v = calculate(EXAMPLE_FAMILIES) + ( + uc_family, + legacy_family, + capped, + lone_parent, + afip, + mixed_age, + hb_lcwra, + hb_floor, + ) = range(8) + # Reg. 3(3): single for UC, a couple for HB, whatever the legacy claim. + for i in (uc_family, legacy_family): + assert v["uc_member_of_couple_claims_as_single_person"][i] + assert v["is_uc_benefit_cap_single_claimant_rate"][i] + assert not v["is_housing_benefit_benefit_cap_single_claimant_rate"][i] + assert v["LHA_category"][i] == "A" + assert v["housing_benefit_LHA_category"][i] == "B" + assert v["claims_legacy_benefits"][legacy_family] + assert v["reference_LHA_category"][uc_family] == "A" + assert v["reference_LHA_category"][legacy_family] == "B" + # Reg. 81: the childcare costs element comes off the UC reduction. + excess = v["benefit_cap_welfare_benefits"][capped] - v["uc_benefit_cap"][capped] + assert excess > 0 + assert v["uc_benefit_cap_reduction"][capped] < excess + assert v["universal_credit"][capped] > v["reference_universal_credit"][capped] + # The HB-only young person: UC single claimant rate, HB lone parent. + assert v["is_uc_benefit_cap_single_claimant_rate"][lone_parent] + assert not v["is_housing_benefit_benefit_cap_single_claimant_rate"][lone_parent] + assert not v["reference_is_benefit_cap_single_claimant_rate"][lone_parent] + # HB's size criteria give the young person a bedroom (reg. 13D(3)(b)), + # so the LHA comparisons leave this family out (invariant 3). + assert v["housing_benefit_LHA_category"][lone_parent] == "C" + assert ( + v["LHA_allowed_bedrooms"][lone_parent] + != v["housing_benefit_LHA_allowed_bedrooms"][lone_parent] + ) + # AFIP: excepted from both shared rates, and lifts both caps, which the + # old lists did not do. + assert v["LHA_category"][afip] == v["housing_benefit_LHA_category"][afip] == "B" + assert v["reference_LHA_category"][afip] == "A" + assert v["is_uc_benefit_cap_exempt"][afip] + assert v["is_housing_benefit_benefit_cap_exempt"][afip] + assert not v["reference_is_benefit_cap_exempt"][afip] + # The mixed-age couple on UC: the UC cap has no age exception, and with + # the younger member on UC the HB Regs 2006 apply (reg. 5(1)(b)); the old + # shared cap exempted any family with a pensioner. + assert v["anyone_sp_age"][mixed_age] + assert v["universal_credit_pre_benefit_cap"][mixed_age] > 0 + assert not v["is_uc_benefit_cap_exempt"][mixed_age] + assert not v["is_housing_benefit_benefit_cap_exempt"][mixed_age] + assert v["reference_is_benefit_cap_exempt"][mixed_age] + # LCWRA is a UC exception (reg. 83(1)(a)), not an HB one (reg. 75F). + assert v["housing_benefit_pre_benefit_cap"][hb_lcwra] > 0 + assert v["is_uc_benefit_cap_exempt"][hb_lcwra] + assert not v["is_housing_benefit_benefit_cap_exempt"][hb_lcwra] + assert v["reference_is_benefit_cap_exempt"][hb_lcwra] + # Reg. 75D(2): the excess is more than the Housing Benefit, which keeps + # 50 pence a week; the old shared reduction took it all. + hb_pre = v["housing_benefit_pre_benefit_cap"][hb_floor] + assert hb_pre > 0 + assert ( + v["benefit_cap_welfare_benefits"][hb_floor] + - v["housing_benefit_benefit_cap"][hb_floor] + > hb_pre + ) + assert abs(v["housing_benefit"][hb_floor] - 0.5 * 52) < 0.01 + assert v["reference_housing_benefit"][hb_floor] == 0 diff --git a/policyengine_uk/tests/test_benefit_cap_specified_benefits_properties.py b/policyengine_uk/tests/test_benefit_cap_specified_benefits_properties.py index a9e2a893f..7d428435e 100644 --- a/policyengine_uk/tests/test_benefit_cap_specified_benefits_properties.py +++ b/policyengine_uk/tests/test_benefit_cap_specified_benefits_properties.py @@ -26,33 +26,42 @@ have limited capability for work-related activity (reg 40(1)(a)(ii)); - the claimant or couple is entitled to working tax credit (HB reg 75E(2)). -The model applies one cap to both schemes, so either scheme's exception -counts. A child or young person is a child under 16, a young person in -non-advanced education, or a 16-year-old (UC reg 5(1)(a)) who does not -receive benefits in their own right (reg 5(5)) and is not looked after -(reg 4(6)). +Each scheme applies its own exceptions. UC reg 83(1)(f) names a "child or +qualifying young person"; HB reg 75F(1)(e) names a "child or young person". +UC includes a 16-year-old school leaver (reg 5(1)(a)) who does not receive +benefits in their own right (reg 5(5)) and is not looked after (reg 4(6)). +HB young persons follow Child Benefit (reg 19), so only the education-based +young-person role counts for Housing Benefit below. UC reg 83(1)(c) names +"a claimant" for attendance allowance, which includes armed forces +independence payment (reg 2); HB reg 75F(1)(ea) also names "a young person" +for it. Only UC reg 83(1)(a), (j) names the LCWRA and carer elements; only +HB reg 75E(2) names entitlement to working tax credit. Invariants: 1. Adding a member who is neither the claimant, the partner nor a child or young person they are responsible for never changes whether the family is - exempt, its cap, its cap reduction or its Universal Credit, whatever - disability or carer benefits, ESA, JSA, incapacity benefit, SDA, - disability flag or caring that member has. The cap counts only the - welfare benefits "to which the single person or couple is entitled" (UC - Regs 2013 reg 80(1); HB Regs 2006 reg 75A). Nor do their age or earnings: - the age exception reads the claimant and partner (Housing Benefit's - pension-age regulations, HB Regs 2006 reg 5) and the earnings exception - the claimants (UC Regs 2013 reg 82(1)(a)), so the draws give the member - State Pension age or earnings at times. -2. The exemption equals a reference written directly from the regulations, - with roles fixed by construction (differential test). -3. An exempt family has no cap and no reduction; any other family has the - single cap if the claimant has no partner and no child or young person, - and the couple-and-family cap otherwise (UC reg 80A(2); HB reg 75CA). + exempt under either scheme, its caps, its reductions, its Universal Credit + or its Housing Benefit, whatever disability or carer benefits, ESA, JSA, + incapacity benefit, SDA, disability flag or caring that member has. The + cap counts only the welfare benefits "to which the single person or + couple is entitled" (UC Regs 2013 reg 80(1); HB Regs 2006 reg 75A). Nor + do their age or earnings: Housing Benefit's age exception reads the + claimant and partner (its pension-age regulations, HB Regs 2006 reg 5), + Universal Credit's the claimants (SI 2014/1230 reg 60C), and the + earnings exception the claimants (UC Regs 2013 reg 82(1)(a)), so the + draws give the member State Pension age or earnings at times. +2. Each scheme's exemption equals its own reference written directly from + the regulations, with roles fixed by construction (differential test). + The union of the two equals the former shared reference. +3. A family exempt under a scheme has no cap and no reduction to that + scheme's award; any other family has that scheme's statutory cap (UC reg + 80A(2); HB reg 75CA) and loses the excess: UC reg 81 deducts the + childcare element from it, and HB reg 75D(2) leaves at least 50p a week. 4. Each head alone: for every role and every circumstance, a family whose only circumstance is that one, held by that member, is exempt exactly when - the reference says (an exhaustive table, for each support-component mode). + that scheme's reference says (an exhaustive table, for each + support-component mode). The properties draw families at random; invariant 4 enumerates them. Each example builds many families in one simulation, in separate households @@ -80,6 +89,20 @@ SINGLE_CAP, FAMILY_CAP = 14_753, 22_020 # Outside London, 2025-26. # A month's element times 12, 2025-26: LCWRA 423.27, carer 201.46. ENTERED_ELEMENTS = {"uc_LCWRA_element": 5_079.24, "uc_carer_element": 2_417.52} +SCHEMES = { + "uc": { + "specified": "is_uc_benefit_cap_exempt_specified_benefit", + "exempt": "is_uc_benefit_cap_exempt", + "cap": "uc_benefit_cap", + "reduction": "uc_benefit_cap_reduction", + }, + "hb": { + "specified": "is_housing_benefit_benefit_cap_exempt_specified_benefit", + "exempt": "is_housing_benefit_benefit_cap_exempt", + "cap": "housing_benefit_benefit_cap", + "reduction": "housing_benefit_benefit_cap_reduction", + }, +} CLAIMANT_OR_PARTNER = ["attendance_allowance", "iidb_reported", "afcs_reported"] CHILD_OR_YOUNG_PERSON = ["dla"] @@ -244,12 +267,17 @@ def own_esa(m): return m["esa_contrib_reported"] > 0 or m["esa_income_reported"] > 0 -def reference_exempt(members, benunit_inputs): - """HB Regs 2006 regs 75E(2), 75F(1) and UC Regs 2013 reg 83(1), read - directly, with roles known by construction.""" +def reference_specified_benefit(members, benunit_inputs, scheme): + """UC Regs 2013 reg 83(1) or HB Regs 2006 reg 75F(1), read directly, with + roles known by construction.""" claimants = [m for m in members if m["_role"] == "claimant"] - young_persons = [m for m in members if m["_role"] in ("young_person", "sixteen")] - hb_young_persons = [m for m in members if m["_role"] == "young_person"] + # UC reg 83 names a "qualifying young person" (reg 5), which includes a + # 16-year-old school leaver (reg 5(1)(a)); HB reg 75F names a "young + # person" (reg 19), a Child Benefit qualifying young person. The + # school-leaver role here has no Child Benefit education or terminal-date + # circumstance, so it is not one. + young_roles = ("young_person", "sixteen") if scheme == "uc" else ("young_person",) + young_persons = [m for m in members if m["_role"] in young_roles] children = [m for m in members if m["_role"] == "child"] # No capital, so the couple's income-related award is their reports. couple_award = any(m["esa_income_reported"] > 0 for m in claimants) @@ -302,21 +330,54 @@ def element_included(element, circumstance): return benunit_inputs[element] > 0 return any(circumstance(m) for m in claimants) - return ( + common = ( any(receives_esa_with_support_component(m) for m in claimants) or any_receives(claimants, CLAIMANT_OR_PARTNER) or any_receives(claimants + children + young_persons, CHILD_OR_YOUNG_PERSON) or any_receives(claimants + young_persons, ["pip_dl"]) or any(entitled_to_carer_benefit(m) for m in claimants + young_persons) - or any_receives(claimants + hb_young_persons, HOUSING_BENEFIT_YOUNG_PERSON) + ) + if scheme == "hb": + # HB reg 75F(1)(ea): armed forces independence payment of the + # claimant, partner or a young person. + return common or any_receives( + claimants + young_persons, HOUSING_BENEFIT_YOUNG_PERSON + ) + # UC reg 83(1)(c): attendance allowance, which includes armed forces + # independence payment (reg 2), of "a claimant"; and the LCWRA and carer + # elements (reg 83(1)(a), (j)), which only Universal Credit has. + return ( + common + or any_receives(claimants, HOUSING_BENEFIT_YOUNG_PERSON) or element_included("uc_LCWRA_element", lcwra) or element_included("uc_carer_element", carer) - or benunit_inputs["working_tax_credit"] > 0 + ) + + +def reference_exempt(members, benunit_inputs, scheme): + """The scheme's exemption. HB reg 75E(2): the claimant or couple is + entitled to working tax credit; Universal Credit has no such exception. + The claimants are under the qualifying age for State Pension Credit and + have no earnings, so neither the age exceptions (HB reg 5; SI 2014/1230 + reg 60C) nor the earnings exception (UC reg 82) applies.""" + return reference_specified_benefit(members, benunit_inputs, scheme) or ( + scheme == "hb" and benunit_inputs["working_tax_credit"] > 0 + ) + + +def reference_union(members, benunit_inputs): + """The former shared reference: either scheme's exemption.""" + return reference_exempt(members, benunit_inputs, "uc") or reference_exempt( + members, benunit_inputs, "hb" ) def reference_cap(members): - # The family-rate helper counts any 16-year-old (UC reg 5(1)(a)); it does + # Each scheme's rate: a single claimant (UC reg 80A(2)(c)) or a claimant + # who neither has a partner nor is a lone parent (HB regs 2(1), 75CA(2)), + # with no child or young person. The family-rate helper counts any + # 16-year-old for the year (UC reg 5(1)(a); for HB, Child Benefit's + # school-leaver terminal dates, which annual ages cannot place); it does # not yet apply reg 5(5) to one receiving benefits in their own right. claimants = sum(m["_role"] == "claimant" for m in members) dependants = any( @@ -327,14 +388,34 @@ def reference_cap(members): CAPPED_FAMILY = { - "universal_credit_pre_benefit_cap": 30_000, - "housing_benefit_pre_benefit_cap": 0, "child_tax_credit": 0, "child_benefit": 0, "income_support": 0, + "uc_childcare_element": 0, } +def capped_family(scheme): + """30,000 of the scheme's award before the cap and none of the other's; + the other family-level capped benefits are nil, so the capped total is + the award plus whatever the claimant's and partner's own benefits add.""" + return { + **CAPPED_FAMILY, + "universal_credit_pre_benefit_cap": 30_000 if scheme == "uc" else 0, + "housing_benefit_pre_benefit_cap": 30_000 if scheme == "hb" else 0, + } + + +def by_scheme(units): + """The units twice, first on Universal Credit and then on Housing + Benefit, each in its own benefit units.""" + return [ + (members, {**inputs, **capped_family(scheme)}) + for scheme in SCHEMES + for members, inputs in units + ] + + SETTINGS = settings( max_examples=40, deadline=None, @@ -348,34 +429,50 @@ def reference_cap(members): def test_non_dependent_adults_never_change_the_exemption(drawn): without = [(members, inputs) for members, _, inputs in drawn] with_other = [(members + [other], inputs) for members, other, inputs in drawn] - # 30,000 of Universal Credit before the cap; the other family-level - # capped benefits are nil, so the capped total is UC plus whatever the - # members' own benefits add. - sim = Simulation( - situation=situation(without + with_other, extra_benunit=CAPPED_FAMILY) - ) + # Each family on Universal Credit and, separately, on Housing Benefit: + # [UC without, UC with, HB without, HB with], k families each. + units = by_scheme(without + with_other) + sim = Simulation(situation=situation(units)) k = len(drawn) claimant_or_partner = sim.calculate("is_claimant_or_partner", YEAR) legacy = sim.calculate("is_child_or_young_person_for_legacy_benefits", YEAR) uc = sim.calculate("is_child_or_qualifying_young_person_for_universal_credit", YEAR) - sizes = [len(members) for members, _ in without + with_other] + sizes = [len(members) for members, _ in units] starts = np.cumsum([0] + sizes[:-1]) - for i in range(k): - # The added member is the last of its benefit unit: neither claimant, - # partner nor anyone's dependant. - last = starts[k + i] + sizes[k + i] - 1 - assert not (claimant_or_partner[last] or legacy[last] or uc[last]), drawn[i] + for offset in (0, 2 * k): + for i in range(k): + # The added member is the last of its benefit unit: neither + # claimant, partner nor anyone's dependant. + family = offset + k + i + last = starts[family] + sizes[family] - 1 + assert not (claimant_or_partner[last] or legacy[last] or uc[last]), drawn[i] + # The model pays one scheme's award, so the family's reduction is that + # scheme's. + reduction = sim.calculate("benefit_cap_reduction", YEAR) + for group, scheme in enumerate(SCHEMES): + other_scheme = "hb" if scheme == "uc" else "uc" + part = slice(group * 2 * k, (group + 1) * 2 * k) + assert not sim.calculate(SCHEMES[other_scheme]["reduction"], YEAR)[part].any() + assert np.array_equal( + reduction[part], sim.calculate(SCHEMES[scheme]["reduction"], YEAR)[part] + ) for variable in [ - "is_benefit_cap_exempt_health_disability", - "is_benefit_cap_exempt_other", - "is_benefit_cap_exempt", - "benefit_cap", + *(name for names in SCHEMES.values() for name in names.values()), + "is_uc_benefit_cap_exempt_earnings", + "is_uc_benefit_cap_exempt_qualifying_age", + "housing_benefit_pension_age_regulations_apply", + "benefit_cap_welfare_benefits", "benefit_cap_reduction", "universal_credit", + "housing_benefit", ]: values = sim.calculate(variable, YEAR) - for i in range(k): - assert values[i] == values[k + i], (variable, drawn[i]) + for offset in (0, 2 * k): + for i in range(k): + assert values[offset + i] == values[offset + k + i], ( + variable, + drawn[i], + ) @SETTINGS @@ -383,29 +480,53 @@ def test_non_dependent_adults_never_change_the_exemption(drawn): def test_exemption_matches_regulations(drawn): units = [(members + [other], inputs) for members, other, inputs in drawn] sim = Simulation(situation=situation(units)) - exempt = sim.calculate("is_benefit_cap_exempt_health_disability", YEAR) + overall = {} + for scheme, names in SCHEMES.items(): + specified = sim.calculate(names["specified"], YEAR) + overall[scheme] = sim.calculate(names["exempt"], YEAR) + for i, (members, inputs) in enumerate(units): + expected = reference_specified_benefit(members, inputs, scheme) + event(f"{scheme} reference exempt: {expected}") + assert specified[i] == expected, (scheme, units[i]) + assert overall[scheme][i] == reference_exempt(members, inputs, scheme), ( + scheme, + units[i], + ) + # Either scheme's exemption: the former shared reference. + union = overall["uc"] | overall["hb"] for i, (members, inputs) in enumerate(units): - expected = reference_exempt(members, inputs) - event(f"reference exempt: {expected}") - assert exempt[i] == expected, units[i] + assert union[i] == reference_union(members, inputs), units[i] @SETTINGS @given(draws()) def test_exempt_families_have_no_cap_and_others_the_statutory_cap(drawn): units = [(members + [other], inputs) for members, other, inputs in drawn] - # 30,000 of Universal Credit alone exceeds either cap. The other - # family-level capped benefits are set to nil; they only add to the excess. - sim = Simulation(situation=situation(units, extra_benunit=CAPPED_FAMILY)) - exempt = sim.calculate("is_benefit_cap_exempt", YEAR) - cap = sim.calculate("benefit_cap", YEAR) - reduction = sim.calculate("benefit_cap_reduction", YEAR) - for i, (members, _) in enumerate(units): - if exempt[i]: - assert np.isinf(cap[i]) and reduction[i] == 0, units[i] - else: - assert cap[i] == reference_cap(members), units[i] - assert reduction[i] >= 30_000 - cap[i], units[i] + # 30,000 of either award alone exceeds either cap. The other family-level + # capped benefits are set to nil; they only add to the excess. + k = len(units) + sim = Simulation(situation=situation(by_scheme(units))) + total = sim.calculate("benefit_cap_welfare_benefits", YEAR) + for group, (scheme, names) in enumerate(SCHEMES.items()): + part = slice(group * k, (group + 1) * k) + exempt = sim.calculate(names["exempt"], YEAR)[part] + cap = sim.calculate(names["cap"], YEAR)[part] + reduction = sim.calculate(names["reduction"], YEAR)[part] + for i, (members, _) in enumerate(units): + if exempt[i]: + assert np.isinf(cap[i]) and reduction[i] == 0, (scheme, units[i]) + else: + assert cap[i] == reference_cap(members), (scheme, units[i]) + excess = max(total[part][i] - cap[i], 0) + # UC reg 81: the excess less the childcare element (nil + # here). HB reg 75D(2): no more than leaves reg 75's 50p a + # week, 30,000 - 0.50 x 52. + expected = excess if scheme == "uc" else min(excess, 30_000 - 26) + assert reduction[i] == pytest.approx(expected, abs=0.01), ( + scheme, + units[i], + ) + assert reduction[i] >= 30_000 - cap[i] - 0.01, (scheme, units[i]) SINGLE_HEAD_ROLES = ["claimant", "partner", *sorted(ROLES), "other"] @@ -440,16 +561,23 @@ def test_each_head_alone_matches_regulations(mode): sim = Simulation( situation=situation([(members, no_entries) for _, members in units]) ) - exempt = sim.calculate("is_benefit_cap_exempt_health_disability", YEAR) - mismatches = [ - (key, bool(exempt[i])) - for i, (key, members) in enumerate(units) - if exempt[i] != reference_exempt(members, no_entries) - ] - assert not mismatches, mismatches - # Every role meets every circumstance, and both outcomes occur. + exemptions = {} + for scheme, names in SCHEMES.items(): + exempt = sim.calculate(names["specified"], YEAR) + exemptions[scheme] = exempt + mismatches = [ + (scheme, key, bool(exempt[i])) + for i, (key, members) in enumerate(units) + if exempt[i] != reference_specified_benefit(members, no_entries, scheme) + ] + assert not mismatches, mismatches + # Both outcomes occur under each scheme. + assert exempt.any() and not exempt.all() + # The schemes differ: UC's elements and 16-year-old school leaver, HB's + # young person's armed forces independence payment. + assert (exemptions["uc"] != exemptions["hb"]).any() + # Every role meets every circumstance. assert len(units) == len(SINGLE_HEAD_ROLES) * len(CIRCUMSTANCES) - assert exempt.any() and not exempt.all() def element_reform(element, amount): @@ -473,6 +601,10 @@ def apply(self): return ReplaceElement +# The Universal Credit exception the elements give (UC Regs 2013 reg +# 83(1)(a), (j)); Housing Benefit has no element exception. +UC_SPECIFIED = SCHEMES["uc"]["specified"] + # The circumstance that gives each element (UC Regs 2013 regs 27(1), 29(1)). # The carer flag is set directly, with no hours of care, so that no # entitlement to Carer's Allowance lifts the cap by another route. @@ -495,7 +627,7 @@ def test_an_element_a_reform_gives_is_decided_by_its_value(element): reform=element_reform(element, 6_000), ) assert positive.calculate(element, YEAR)[0] == 6_000 - assert positive.calculate("is_benefit_cap_exempt_health_disability", YEAR)[0] + assert positive.calculate(UC_SPECIFIED, YEAR)[0] # A reform's amount that equals the model's own (here, what the model's # formula gives for the other member's circumstance) is still the # reform's. @@ -508,7 +640,7 @@ def test_an_element_a_reform_gives_is_decided_by_its_value(element): reform=element_reform(element, model_amount), ) assert same.calculate(element, YEAR)[0] == model_amount - assert same.calculate("is_benefit_cap_exempt_health_disability", YEAR)[0] + assert same.calculate(UC_SPECIFIED, YEAR)[0] claimant = blank(role="claimant", age=40) claimant[circumstance] = True @@ -517,14 +649,14 @@ def test_an_element_a_reform_gives_is_decided_by_its_value(element): reform=element_reform(element, 0), ) assert nil.calculate(element, YEAR)[0] == 0 - assert not nil.calculate("is_benefit_cap_exempt_health_disability", YEAR)[0] + assert not nil.calculate(UC_SPECIFIED, YEAR)[0] # Without the reform the claimant's circumstance gives the element and # lifts the cap. baseline = Simulation( situation=situation([([claimant], {"working_tax_credit": 0})]) ) assert baseline.calculate(element, YEAR)[0] > 0 - assert baseline.calculate("is_benefit_cap_exempt_health_disability", YEAR)[0] + assert baseline.calculate(UC_SPECIFIED, YEAR)[0] def test_the_rebalancing_adds_no_entry_and_keeps_one(): @@ -541,7 +673,7 @@ def test_the_rebalancing_adds_no_entry_and_keeps_one(): assert calculated.calculate("uc_LCWRA_element", year)[0] > 0 benunit = calculated.populations["benunit"] assert supplied_input(benunit, "uc_LCWRA_element", period(year)) is None - assert not calculated.calculate("is_benefit_cap_exempt_health_disability", year)[0] + assert not calculated.calculate(UC_SPECIFIED, year)[0] entered = Simulation( situation=situation( @@ -551,7 +683,7 @@ def test_the_rebalancing_adds_no_entry_and_keeps_one(): ) benunit = entered.populations["benunit"] assert supplied_input(benunit, "uc_LCWRA_element", period(year)) is not None - assert entered.calculate("is_benefit_cap_exempt_health_disability", year)[0] + assert entered.calculate(UC_SPECIFIED, year)[0] # An element is an entry only while the value stored is one: the model's own @@ -622,9 +754,7 @@ def test_an_entry_equal_to_the_model_value_is_an_entry(enter): same simulation, however it is entered.""" year = 2026 without_entry = Simulation(situation=element_situation(year)) - assert not without_entry.calculate("is_benefit_cap_exempt_health_disability", year)[ - 0 - ] + assert not without_entry.calculate(UC_SPECIFIED, year)[0] simulation = Simulation(situation=element_situation(year)) model_amount = float(simulation.calculate("uc_LCWRA_element", year)[0]) assert model_amount > 0 @@ -637,11 +767,11 @@ def test_an_entry_equal_to_the_model_value_is_an_entry(enter): model_amount ) assert is_entered(simulation, year) - assert simulation.calculate("is_benefit_cap_exempt_health_disability", year)[0] + assert simulation.calculate(UC_SPECIFIED, year)[0] # As in a simulation built with the entry. fresh = Simulation(situation=element_situation(year, model_amount)) assert is_entered(fresh, year) - assert fresh.calculate("is_benefit_cap_exempt_health_disability", year)[0] + assert fresh.calculate(UC_SPECIFIED, year)[0] def test_clones_and_branches_keep_their_own_record_of_model_values(): @@ -666,7 +796,7 @@ def test_clones_and_branches_keep_their_own_record_of_model_values(): assert simulation.calculate("uc_LCWRA_element", year)[0] == pytest.approx( model_amount ) - assert not simulation.calculate("is_benefit_cap_exempt_health_disability", year)[0] + assert not simulation.calculate(UC_SPECIFIED, year)[0] @pytest.fixture(scope="module") @@ -714,10 +844,7 @@ def test_an_input_makes_the_element_an_entry_only_in_years_it_stores( for year in REBALANCED_YEARS: entered = year in entered_years assert is_entered(simulation, year) == entered, year - assert ( - simulation.calculate("is_benefit_cap_exempt_health_disability", year)[0] - == entered - ), year + assert simulation.calculate(UC_SPECIFIED, year)[0] == entered, year def test_an_inconsistent_input_for_part_of_a_year_keeps_the_model_value( diff --git a/policyengine_uk/tests/test_benefit_cap_uc_and_hb_properties.py b/policyengine_uk/tests/test_benefit_cap_uc_and_hb_properties.py new file mode 100644 index 000000000..41d110eb8 --- /dev/null +++ b/policyengine_uk/tests/test_benefit_cap_uc_and_hb_properties.py @@ -0,0 +1,135 @@ +"""A family receiving both Universal Credit and Housing Benefit is capped once. + +UC Regs 2013 reg 80(2A): where the welfare benefit is Housing Benefit, "the +amount to be used is nil". HB Regs 2006 reg 75F(1)(g): the Housing Benefit +cap does not apply where "the claimant is receiving universal credit". A +family paid Housing Benefit alongside Universal Credit (for specified or +temporary accommodation) is therefore capped only through Universal Credit, +on its other welfare benefits. + +The model's Housing Benefit eligibility rules out a family that claims +Universal Credit (housing_benefit_eligible), so both awards are supplied here +as inputs (universal_credit_pre_benefit_cap, housing_benefit_pre_benefit_cap). + +Invariants, for every family: + +1. Housing Benefit changes no Universal Credit cap reduction: a family's + reduction equals that of the same family with no Housing Benefit (reg + 80(2A)), and it is reg 81's excess over the cap, less the childcare + element, of the welfare benefits without Housing Benefit. +2. Housing Benefit counts in the welfare benefits at its amount before the + cap (HB Regs 2006 reg 75C(2)(b)): the total rises by exactly that amount. +3. A family receiving Universal Credit has no Housing Benefit reduction, and + its benefit cap reduction is its Universal Credit reduction alone. If it + has Housing Benefit, it is exempt from the Housing Benefit cap (reg + 75F(1)(g)). +4. A family without Universal Credit has no Universal Credit reduction, and + its Housing Benefit reduction is reg 75D's: the excess over the Housing + Benefit cap, leaving at least 50 pence a week. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +YEAR = 2025 +SINGLE_CAP = 14_753 # Outside London, 2025-26 (UC reg 80A(2); HB reg 75CA(2)). +HB_MINIMUM = 0.50 * 52 # HB Regs 2006 reg 75: 50 pence a week. + +FAMILY = st.fixed_dictionaries( + { + "universal_credit": st.one_of(st.just(0), st.integers(1, 30_000)), + "housing_benefit": st.integers(0, 20_000), + "jsa_contrib": st.integers(0, 30_000), + "childcare": st.integers(0, 10_000), + } +) + + +def situation(families): + """Each family twice: with its Housing Benefit, then with none.""" + people, benunits, households = {}, {}, {} + units = [(f, f["housing_benefit"]) for f in families] + [(f, 0) for f in families] + for i, (family, housing_benefit) in enumerate(units): + person = f"p{i}" + people[person] = { + "age": {YEAR: 30}, + "is_claimant_or_partner": {YEAR: True}, + "jsa_contrib": {YEAR: family["jsa_contrib"]}, + } + benunits[f"b{i}"] = { + "members": [person], + "universal_credit_pre_benefit_cap": {YEAR: family["universal_credit"]}, + "housing_benefit_pre_benefit_cap": {YEAR: housing_benefit}, + "uc_childcare_element": {YEAR: family["childcare"]}, + } + households[f"h{i}"] = { + "members": [person], + "region": {YEAR: "NORTH_EAST"}, + "country": {YEAR: "ENGLAND"}, + } + return {"people": people, "benunits": benunits, "households": households} + + +@settings( + max_examples=12, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) +@given(st.lists(FAMILY, min_size=1, max_size=8)) +def test_a_family_with_both_awards_is_capped_once_through_universal_credit( + families, +): + sim = Simulation(situation=situation(families)) + n = len(families) + + def calc(name): + values = sim.calculate(name, YEAR) + return values[:n], values[n:] + + uc, uc_without_hb = calc("uc_benefit_cap_reduction") + hb_reduction, _ = calc("housing_benefit_benefit_cap_reduction") + total_reduction, _ = calc("benefit_cap_reduction") + welfare, welfare_without_hb = calc("benefit_cap_welfare_benefits") + hb_exempt, _ = calc("is_housing_benefit_benefit_cap_exempt") + uc_cap, _ = calc("uc_benefit_cap") + hb_cap, _ = calc("housing_benefit_benefit_cap") + + on_uc = np.array([f["universal_credit"] > 0 for f in families]) + housing_benefit = np.array([f["housing_benefit"] for f in families], float) + childcare = np.array([f["childcare"] for f in families], float) + + # 1. Reg 80(2A). + np.testing.assert_allclose(uc, uc_without_hb, atol=0.01) + assert (uc_cap == SINGLE_CAP).all() + expected_uc = np.where( + on_uc, + np.maximum( + np.maximum(welfare - housing_benefit - uc_cap, 0) - childcare, + 0, + ), + 0, + ) + np.testing.assert_allclose(uc, expected_uc, atol=0.01) + + # 2. Reg 75C(2)(b). + np.testing.assert_allclose(welfare - welfare_without_hb, housing_benefit, atol=0.01) + + # 3. Reg 75F(1)(g). + assert hb_exempt[on_uc & (housing_benefit > 0)].all() + assert (hb_reduction[on_uc] == 0).all() + np.testing.assert_allclose(total_reduction[on_uc], uc[on_uc], atol=0.01) + + # 4. Reg 75D for a family on Housing Benefit alone. + off = ~on_uc + assert (uc[off] == 0).all() + assert (~hb_exempt[off]).all() + assert (hb_cap[off] == SINGLE_CAP).all() + expected_hb = np.minimum( + np.maximum(welfare[off] - SINGLE_CAP, 0), + np.maximum(housing_benefit[off] - HB_MINIMUM, 0), + ) + np.testing.assert_allclose(hb_reduction[off], expected_hb, atol=0.01) + np.testing.assert_allclose(total_reduction[off], expected_hb, atol=0.01) diff --git a/policyengine_uk/tests/test_data_built_imputations.py b/policyengine_uk/tests/test_data_built_imputations.py index a246fc9e7..2912733f0 100644 --- a/policyengine_uk/tests/test_data_built_imputations.py +++ b/policyengine_uk/tests/test_data_built_imputations.py @@ -55,7 +55,7 @@ "members": ["adult", "child"], "would_claim_uc": {YEAR: True}, "universal_credit_pre_benefit_cap": {YEAR: 6_000}, - "benefit_cap_reduction": {YEAR: 0}, + "uc_benefit_cap_reduction": {YEAR: 0}, } }, "households": { @@ -91,7 +91,7 @@ def tables(n: int = 400, weight_scale: float = 1.0) -> dict: "benunit_id": ids, "would_claim_uc": np.ones(n, dtype=bool), "universal_credit_pre_benefit_cap": np.full(n, 6_000.0), - "benefit_cap_reduction": np.zeros(n), + "uc_benefit_cap_reduction": np.zeros(n), } ) household = pd.DataFrame( diff --git a/policyengine_uk/tests/test_legacy_award_readers_properties.py b/policyengine_uk/tests/test_legacy_award_readers_properties.py index be9153997..62efc0e89 100644 --- a/policyengine_uk/tests/test_legacy_award_readers_properties.py +++ b/policyengine_uk/tests/test_legacy_award_readers_properties.py @@ -111,7 +111,8 @@ "council_tax_reduction_applicable_income", "council_tax_reduction_relevant_income_based_benefit", "tax_credits_applicable_income", - "is_benefit_cap_exempt_health_disability", + "is_uc_benefit_cap_exempt_specified_benefit", + "is_housing_benefit_benefit_cap_exempt_specified_benefit", "targeted_childcare_entitlement_eligible", "would_claim_IS", "income_support_eligible", diff --git a/policyengine_uk/tests/test_state_pension_credit_qualifying_age.py b/policyengine_uk/tests/test_state_pension_credit_qualifying_age.py index a1eea4e24..7748b39a2 100644 --- a/policyengine_uk/tests/test_state_pension_credit_qualifying_age.py +++ b/policyengine_uk/tests/test_state_pension_credit_qualifying_age.py @@ -184,7 +184,11 @@ def assert_consumers_follow_the_qualifying_age(births, male, year): assert np.array_equal( sim.calculate("council_tax_reduction_pensioner", year), attained ) - assert np.array_equal(sim.calculate("is_benefit_cap_exempt_other", year), attained) + # HB Regs 2006 reg 5: Housing Benefit under the pension-age regulations + # has no cap. + assert np.array_equal( + sim.calculate("is_housing_benefit_benefit_cap_exempt", year), attained + ) aged = sim.tax_benefit_system.parameters( f"{year}-06-01" ).gov.dwp.housing_benefit.allowances.single.aged diff --git a/policyengine_uk/tests/test_trading_loss_properties.py b/policyengine_uk/tests/test_trading_loss_properties.py index c74afc311..4f6d16034 100644 --- a/policyengine_uk/tests/test_trading_loss_properties.py +++ b/policyengine_uk/tests/test_trading_loss_properties.py @@ -91,7 +91,7 @@ UC_TESTS = [ "uc_earned_income", "universal_credit_pre_benefit_cap", - "is_benefit_cap_exempt_earnings", + "is_uc_benefit_cap_exempt_earnings", "universal_credit", ] # HBAI components the loss may move: the loss itself, taxes, and benefits diff --git a/policyengine_uk/tests/test_uc_claimant_income_properties.py b/policyengine_uk/tests/test_uc_claimant_income_properties.py index b03ea77dd..19ac9c1b7 100644 --- a/policyengine_uk/tests/test_uc_claimant_income_properties.py +++ b/policyengine_uk/tests/test_uc_claimant_income_properties.py @@ -300,7 +300,7 @@ def test_dependants_earnings_do_not_lift_the_benefit_cap(units, year): err_msg=f"{v}: {units}", ) assert not np.any( - np.asarray(with_income.calculate("is_benefit_cap_exempt_earnings", year)) + np.asarray(with_income.calculate("is_uc_benefit_cap_exempt_earnings", year)) ), units diff --git a/policyengine_uk/tests/test_uc_company_owner_properties.py b/policyengine_uk/tests/test_uc_company_owner_properties.py index 102ca3bdc..84301ea83 100644 --- a/policyengine_uk/tests/test_uc_company_owner_properties.py +++ b/policyengine_uk/tests/test_uc_company_owner_properties.py @@ -96,7 +96,7 @@ "universal_credit", "uc_assessable_capital", "uc_earned_income", - "is_benefit_cap_exempt_earnings", + "is_uc_benefit_cap_exempt_earnings", ] PERSON_VARIABLES = [ "uc_individual_earned_income", @@ -542,7 +542,7 @@ def test_universal_credit_monotone_in_company_facts(unit_list, bump, asset_bump) ] got = calculate(variants) uc = got["universal_credit"].reshape(-1, 6) - exempt = got["is_benefit_cap_exempt_earnings"].reshape(-1, 6) + exempt = got["is_uc_benefit_cap_exempt_earnings"].reshape(-1, 6) tol = 1e-6 base, more_income, more_capital, more_holding, more_assets, engaged = uc.T # Extra earnings that lift the benefit cap (reg. 82(1)(a)) can raise the diff --git a/policyengine_uk/tests/test_uc_deductions.py b/policyengine_uk/tests/test_uc_deductions.py index ffab86dfe..a06e740ba 100644 --- a/policyengine_uk/tests/test_uc_deductions.py +++ b/policyengine_uk/tests/test_uc_deductions.py @@ -15,7 +15,7 @@ def make_situation(**benunit_overrides): "members": ["person"], "would_claim_uc": {YEAR: True}, "universal_credit_pre_benefit_cap": {YEAR: 6_000}, - "benefit_cap_reduction": {YEAR: 0}, + "uc_benefit_cap_reduction": {YEAR: 0}, **benunit_overrides, } return { @@ -103,7 +103,7 @@ def test_protected_floor_limits_combined_reductions(self): # of the standard allowance; the floor limits the combined # reductions to (1 - 0.85) x standard allowance. situation = make_situation( - benefit_cap_reduction={YEAR: 2_500}, + uc_benefit_cap_reduction={YEAR: 2_500}, uc_latent_deduction_rate={YEAR: 0.25}, uc_deduction_combination={YEAR: "ADVANCE_ONLY"}, ) diff --git a/policyengine_uk/tests/test_uc_income_from_capital_properties.py b/policyengine_uk/tests/test_uc_income_from_capital_properties.py index cfeeda4c5..2d0b1b2c8 100644 --- a/policyengine_uk/tests/test_uc_income_from_capital_properties.py +++ b/policyengine_uk/tests/test_uc_income_from_capital_properties.py @@ -89,7 +89,7 @@ "uc_maximum_amount", "uc_assessable_capital", "is_uc_eligible", - "is_benefit_cap_exempt", + "is_uc_benefit_cap_exempt", ] @@ -255,14 +255,16 @@ def test_interest_dividends_and_rent_leave_the_award_unchanged(units, scale, yea unchanged = np.isclose( scaled["personal_allowances"], base["personal_allowances"], atol=0.01 ) - # Compare the capped award only where the exemption is the same: - # capital income can change another exemption route, such as Working - # Tax Credit or the pension-age Housing Benefit rules. + # Compare the capped award only where the Universal Credit exemption is + # the same, so that a change of exemption is not read as a change in the + # award. The routes through which capital income moved the shared + # exemption, Working Tax Credit and the pension-age Housing Benefit + # rules, are now Housing Benefit's alone (is_housing_benefit_benefit_cap_exempt). same_cap = unchanged & ( - scaled["is_benefit_cap_exempt"] == base["is_benefit_cap_exempt"] + scaled["is_uc_benefit_cap_exempt"] == base["is_uc_benefit_cap_exempt"] ) for variable in UC_VARIABLES: - if variable == "is_benefit_cap_exempt": + if variable == "is_uc_benefit_cap_exempt": continue compared = same_cap if variable == "universal_credit" else unchanged np.testing.assert_allclose( diff --git a/policyengine_uk/tests/test_uc_legacy_closure_properties.py b/policyengine_uk/tests/test_uc_legacy_closure_properties.py index 2e95eed8c..38d7d0b8c 100644 --- a/policyengine_uk/tests/test_uc_legacy_closure_properties.py +++ b/policyengine_uk/tests/test_uc_legacy_closure_properties.py @@ -106,7 +106,8 @@ "uc_assessable_capital", "uc_housing_costs_element", "is_lha_shared_accommodation_rate_specified_renter", - "is_benefit_cap_single_claimant_rate", + "is_uc_benefit_cap_single_claimant_rate", + "uc_benefit_cap_reduction", ] award = st.sampled_from([0.0, 0.0, 2_000.0, 6_000.0]) @@ -370,7 +371,8 @@ def test_a_closure_claim_pays_the_universal_credit_of_any_claim(units): for benefit in BENEFITS + [ "uc_housing_costs_element", "is_lha_shared_accommodation_rate_specified_renter", - "is_benefit_cap_single_claimant_rate", + "is_uc_benefit_cap_single_claimant_rate", + "uc_benefit_cap_reduction", ]: assert ( abs(float(v[benefit][i]) - float(anyway[year][benefit][i])) diff --git a/policyengine_uk/tests/test_uc_single_claim_properties.py b/policyengine_uk/tests/test_uc_single_claim_properties.py index b6c5b5a33..f7dbea924 100644 --- a/policyengine_uk/tests/test_uc_single_claim_properties.py +++ b/policyengine_uk/tests/test_uc_single_claim_properties.py @@ -29,29 +29,29 @@ claimants', and the sum of each member's capital calculated alone, the other member's Lifetime ISA included. 5. Benefit cap: in families whose children are all under 16, the - single-claimant rate applies exactly when there is no child (reg. 80A(2); - a 16 or 17-year-old is a qualifying young person only in qualifying - education, which the generator leaves unset). The other member's - disability benefits, LCWRA, caring, AFCS and contributory ESA lift no cap - (reg. 83(1)), so both cap exceptions equal the joint claimants' with those - removed. The AFCS and ESA heads are in the first exception - (is_benefit_cap_exempt_health_disability), with a claimant's AFCS or - contributory ESA lifting it; the model takes contributory ESA of a - person's own as including the support component unless told otherwise - (reg. 83(1)(a) needs the support component). The second exception - (is_benefit_cap_exempt_other) is met only where - Housing Benefit falls under the pension-age regulations, which the model - applies to a family with a member over the qualifying age for State - Pension Credit and no Universal Credit award. Whether there is an award - can differ between the single claim and the stripped calculation, either - way round. The single claim has a single claimant's amounts (reg. 36(3)) - and still counts the other member's income, such as contributory ESA - (reg. 22(3)), so its award can be the lower. It also keeps the work - allowance the other member's limited capability gives (reg. 22(3)), - which stripping removes, so its award can be the higher. So each - calculation's second exception is checked against its own route, and the - two are compared only for families the pension-age route reaches in - neither (see off_pension_age_route). + Universal Credit single-claimant rate applies exactly when there is no + child (reg. 80A(2); a 16 or 17-year-old is a qualifying young person only + in qualifying education, which the generator leaves unset). The other + member's disability benefits, LCWRA, caring, AFCS and contributory ESA + lift no Universal Credit cap (reg. 83(1)), so the Universal Credit + exceptions equal the joint claimants' with those removed, and a + claimant's own AFCS or contributory ESA lifts it. The model takes + contributory ESA of a person's own as including the support component + unless told otherwise (esa_includes_support_component; reg. 83(1)(a) + needs the support component). + Housing Benefit has no single claim by a member of a couple, so its cap + rate, exceptions and shared accommodation test (HB Regs 2006 regs. 2(1), + 13D(2), 75CA, 75E, 75F) and its LHA category are the joint claimants' + whatever the flag. Its cap exception is also met where Housing Benefit + falls under the pension-age regulations, which the model applies to a + family with a member over the qualifying age for State Pension Credit and + no Universal Credit award. Whether there is an award can differ between + the single claim and the joint claimants, either way round: the single + claim has a single claimant's amounts (reg. 36(3)) but still counts the + other member's income (reg. 22(3)). So within each calculation the route + lifts the Housing Benefit cap wherever it applies, and the two are + compared only for families the route reaches in neither (see + off_pension_age_route). 6. Regulation 3(3)(a): with the flag left to its formula, a member of a couple under 18 is the ineligible partner exactly when none of the generated regulation 8(1) circumstances applies to them (limited @@ -59,12 +59,13 @@ child under 16 in the family), or they are under 16; the other member then claims as a single person. 7. A flag that marks no single claim (a single adult, or both members of a - couple) leaves every rule Housing Benefit shares (the cap rate, the cap - exceptions, the shared accommodation test) as it was without the flag. - Such a flag leaves no one able to claim Universal Credit, so for the - second cap exception each calculation is again checked against its own - route, and families on the pension-age route in either calculation are - left out of the comparison (see off_pension_age_route). + couple) leaves the Universal Credit cap rate, cap exceptions and shared + accommodation test, and their Housing Benefit counterparts, as they were + without the flag. Such a flag leaves no one able to claim Universal + Credit, so families on the pension-age route in either calculation are + left out of the comparison of the Housing Benefit cap exception (see + off_pension_age_route), and each calculation's Universal Credit exception + is checked against its own inputs (a claimant's AFCS or contributory ESA). Each property also runs on EXAMPLE_FAMILIES, built so that the cases the properties are about occur, each in a family of its own: an other member @@ -337,10 +338,14 @@ def situation(families, year, mode): "uc_housing_costs_element", "is_uc_eligible", "is_uc_work_allowance_eligible", - "is_benefit_cap_single_claimant_rate", - "is_benefit_cap_exempt_health_disability", - "is_benefit_cap_exempt_other", + "is_uc_benefit_cap_single_claimant_rate", + "is_uc_benefit_cap_exempt_specified_benefit", + "is_uc_benefit_cap_exempt", "is_lha_shared_accommodation_rate_specified_renter", + "is_housing_benefit_benefit_cap_single_claimant_rate", + "is_housing_benefit_benefit_cap_exempt_specified_benefit", + "is_housing_benefit_benefit_cap_exempt", + "is_housing_benefit_young_individual", "housing_benefit_pension_age_regulations_apply", ] PENSION_AGE_ROUTE = "housing_benefit_pension_age_regulations_apply" @@ -349,9 +354,9 @@ def situation(families, year, mode): def off_pension_age_route(*calculations): """The families that no calculation puts on the pension-age route. - is_benefit_cap_exempt_other also lifts the cap where Housing Benefit falls - under the pension-age regulations (HB (SPC) Regs 2006 reg. 5; the cap's - age exception, SI 2014/1230 reg. 60C). The model takes that route for a + is_housing_benefit_benefit_cap_exempt also lifts the cap where Housing + Benefit falls under the pension-age regulations (HB (SPC) Regs 2006 reg. + 5); Universal Credit has no such exception. The model takes that route for a family with a claimant or partner over the qualifying age for State Pension Credit and no Universal Credit award. Whether there is an award can differ between the calculations, either way round: a single claim has @@ -362,30 +367,22 @@ def off_pension_age_route(*calculations): calculation the route lifts the cap wherever it applies. """ for values in calculations: - assert np.all(values["is_benefit_cap_exempt_other"][values[PENSION_AGE_ROUTE]]) + assert np.all( + values["is_housing_benefit_benefit_cap_exempt"][values[PENSION_AGE_ROUTE]] + ) return ~np.logical_or.reduce([values[PENSION_AGE_ROUTE] for values in calculations]) -def check_cap_exceptions_against_inputs(families, values, members): - """Each cap exception as the inputs give it. - - is_benefit_cap_exempt_other is the pension-age route as the same - calculation gives it (taken from the model; - test_benefit_cap_and_ctr_pension_age_properties.py and - benefit_cap_pension_age.yaml test the route itself). AFCS or contributory - ESA of one of ``members`` lifts is_benefit_cap_exempt_health_disability - (reg. 83(1)(a) and (e); the model takes contributory ESA of a person's - own as including the support component unless told otherwise). - ``members`` are the people whose own benefits count: the claimant alone - on a single claim, in a stripped calculation (the other member's - benefits removed) or with no partner, and both members of a couple - neither of whom is excluded. +def assert_own_afcs_or_esa_lifts_the_uc_cap(families, values, members): + """A claimant's AFCS or contributory ESA lifts the Universal Credit cap. + + Reg. 83(1)(a) and (e); the model takes contributory ESA of a person's own + as including the support component unless told otherwise. ``members`` + are the people whose own benefits count: the claimant alone on a single + claim, in a stripped calculation (the other member's benefits removed) + or with no partner, and both members of a couple neither of whom is + excluded. """ - np.testing.assert_array_equal( - values["is_benefit_cap_exempt_other"], - values[PENSION_AGE_ROUTE], - err_msg=f"is_benefit_cap_exempt_other: {families}", - ) own = np.array( [ any( @@ -395,7 +392,7 @@ def check_cap_exceptions_against_inputs(families, values, members): for family in families ] ) - assert np.all(values["is_benefit_cap_exempt_health_disability"][own]), families + assert np.all(values["is_uc_benefit_cap_exempt_specified_benefit"][own]), families def calculate(families, year, mode, extra=()): @@ -404,6 +401,8 @@ def calculate(families, year, mode, extra=()): values["claimant_type"] = np.asarray( sim.calculate("uc_standard_allowance_claimant_type", year) ).astype(str) + for v in ["LHA_category", "housing_benefit_LHA_category"]: + values[v] = np.asarray(sim.calculate(v, year)).astype(str) for v in extra: values[v] = np.asarray(sim.calculate(v, year)) values["parameters"] = sim.tax_benefit_system.parameters(f"{year}-01-01") @@ -542,6 +541,7 @@ def test_capital_includes_the_other_members(families, year, balances): def test_benefit_cap_rate_and_exceptions(families, year): single = calculate(families, year, "single") stripped = calculate(families, year, "stripped") + joint = calculate(families, year, "joint") # Reg. 80A(2): the single-claimant limit unless responsible for a child # or qualifying young person. A 16 or 17-year-old is a qualifying young # person only in qualifying education, which the generator leaves @@ -551,22 +551,39 @@ def test_benefit_cap_rate_and_exceptions(families, year): [all(age < 16 for age in family["children"]) for family in families] ) np.testing.assert_array_equal( - single["is_benefit_cap_single_claimant_rate"][under_16_only], + single["is_uc_benefit_cap_single_claimant_rate"][under_16_only], no_children[under_16_only], ) - np.testing.assert_array_equal( - single["is_benefit_cap_exempt_health_disability"], - stripped["is_benefit_cap_exempt_health_disability"], - err_msg=f"is_benefit_cap_exempt_health_disability: {families}", - ) + for variable in [ + "is_uc_benefit_cap_exempt_specified_benefit", + "is_uc_benefit_cap_exempt", + ]: + np.testing.assert_array_equal( + single[variable], stripped[variable], err_msg=f"{variable}: {families}" + ) for values in [single, stripped]: - check_cap_exceptions_against_inputs(families, values, ["claimant"]) - off_route = off_pension_age_route(single, stripped) + assert_own_afcs_or_esa_lifts_the_uc_cap(families, values, ["claimant"]) + # Housing Benefit has no single claim by a member of a couple: its rules + # see the couple whatever the Universal Credit flag says. + for variable in [ + "is_housing_benefit_benefit_cap_single_claimant_rate", + "is_housing_benefit_benefit_cap_exempt_specified_benefit", + "is_housing_benefit_young_individual", + "housing_benefit_LHA_category", + ]: + np.testing.assert_array_equal( + single[variable], joint[variable], err_msg=f"{variable}: {families}" + ) + off_route = off_pension_age_route(single, joint) np.testing.assert_array_equal( - single["is_benefit_cap_exempt_other"][off_route], - stripped["is_benefit_cap_exempt_other"][off_route], - err_msg=f"is_benefit_cap_exempt_other: {families}", + single["is_housing_benefit_benefit_cap_exempt"][off_route], + joint["is_housing_benefit_benefit_cap_exempt"][off_route], + err_msg=f"is_housing_benefit_benefit_cap_exempt: {families}", ) + # HB reg. 2(1): a claimant with a partner is never a single claimant or a + # young individual. + assert not np.any(single["is_housing_benefit_benefit_cap_single_claimant_rate"]) + assert not np.any(single["is_housing_benefit_young_individual"]) @PROPERTY_SETTINGS @@ -615,12 +632,18 @@ def test_a_partner_under_18_outside_regulation_8_cannot_claim_jointly( @PROPERTY_SETTINGS @given(families=populations, year=st.sampled_from(YEARS)) @example(families=example_families(), year=2026) -def test_a_flag_marking_no_single_claim_leaves_shared_rules_alone(families, year): +def test_a_flag_marking_no_single_claim_leaves_cap_and_lha_rules_alone(families, year): shared = [ - "is_benefit_cap_single_claimant_rate", - "is_benefit_cap_exempt_health_disability", - "is_benefit_cap_exempt_other", + "is_uc_benefit_cap_single_claimant_rate", + "is_uc_benefit_cap_exempt_specified_benefit", + "is_uc_benefit_cap_exempt", "is_lha_shared_accommodation_rate_specified_renter", + "LHA_category", + "is_housing_benefit_benefit_cap_single_claimant_rate", + "is_housing_benefit_benefit_cap_exempt_specified_benefit", + "is_housing_benefit_benefit_cap_exempt", + "is_housing_benefit_young_individual", + "housing_benefit_LHA_category", ] both = calculate(families, year, "both") joint = calculate(families, year, "joint") @@ -636,13 +659,13 @@ def test_a_flag_marking_no_single_claim_leaves_shared_rules_alone(families, year (alone_flagged, ["claimant"]), (solo, ["claimant"]), ]: - check_cap_exceptions_against_inputs(families, values, members) + assert_own_afcs_or_esa_lifts_the_uc_cap(families, values, members) for flagged, unflagged in [(both, joint), (alone_flagged, solo)]: off_route = off_pension_age_route(flagged, unflagged) for variable in shared: compared = ( off_route - if variable == "is_benefit_cap_exempt_other" + if variable == "is_housing_benefit_benefit_cap_exempt" else np.ones_like(off_route) ) np.testing.assert_array_equal( @@ -670,21 +693,27 @@ def test_examples_reach_the_cases(): assert single["uc_carer_element"][family] == 0 < joint["uc_carer_element"][family] assert single["uc_carer_element"][i["carer_claimant"]] > 0 # Reg. 83(1): the other member's LCWRA and PIP, AFCS or ESA lift the - # joint claimants' cap only; the claimant's AFCS lifts it. - family = i["young_renter"] - assert not single["is_benefit_cap_exempt_health_disability"][family] - assert joint["is_benefit_cap_exempt_health_disability"][family] - for family in [i["afcs_partner"], i["esa_partner"]]: - assert not single["is_benefit_cap_exempt_health_disability"][family] - assert joint["is_benefit_cap_exempt_health_disability"][family] - # The AFCS and ESA heads are not age exceptions. - assert not joint["is_benefit_cap_exempt_other"][family] - assert single["is_benefit_cap_exempt_health_disability"][i["afcs_claimant"]] + # joint claimants' cap only; the claimant's AFCS lifts it. HB reg. + # 75F(1): the partner's benefits lift the HB cap either way. + for family in [i["young_renter"], i["afcs_partner"], i["esa_partner"]]: + assert not single["is_uc_benefit_cap_exempt_specified_benefit"][family] + assert joint["is_uc_benefit_cap_exempt_specified_benefit"][family] + assert single["is_housing_benefit_benefit_cap_exempt_specified_benefit"][family] + assert single["is_uc_benefit_cap_exempt_specified_benefit"][i["afcs_claimant"]] # Reg. 80A(2) and Sch 4 para 28(2): single rate and shared accommodation. + # HB regs. 2(1), 13D(2) and 75CA(2): a couple either way. family = i["young_renter"] - assert single["is_benefit_cap_single_claimant_rate"][family] - assert not joint["is_benefit_cap_single_claimant_rate"][family] + assert single["is_uc_benefit_cap_single_claimant_rate"][family] + assert not joint["is_uc_benefit_cap_single_claimant_rate"][family] + assert not single["is_housing_benefit_benefit_cap_single_claimant_rate"][family] assert single["is_lha_shared_accommodation_rate_specified_renter"][family] assert not joint["is_lha_shared_accommodation_rate_specified_renter"][family] + assert single["LHA_category"][family] == "A" + assert not single["is_housing_benefit_young_individual"][family] + assert ( + single["housing_benefit_LHA_category"][family] + == joint["housing_benefit_LHA_category"][family] + == "B" + ) # Reg. 22(3): the other member's LCW gives the work allowance either way. assert single["is_uc_work_allowance_eligible"][family] diff --git a/policyengine_uk/tests/test_uc_statutory_pay_properties.py b/policyengine_uk/tests/test_uc_statutory_pay_properties.py index fdf11d88d..3eb8ae1fd 100644 --- a/policyengine_uk/tests/test_uc_statutory_pay_properties.py +++ b/policyengine_uk/tests/test_uc_statutory_pay_properties.py @@ -41,7 +41,7 @@ 82(1)(a) threshold, and the payments are earned income (reg. 55(4)). So a pound of pay can lift the cap and raise the final award. The award after the cap may therefore rise only where the family becomes exempt - (is_benefit_cap_exempt); otherwise it obeys the same bound. The + (is_uc_benefit_cap_exempt); otherwise it obeys the same bound. The earnings exemption uses actual UC net earned income before the minimum income floor, which reg. 82(4) excludes. @@ -302,8 +302,8 @@ def test_statutory_sick_pay_preserves_benefit_cap_earnings_exemption(): atol=TOLERANCE, rtol=0, ) - assert results.families("is_benefit_cap_exempt_earnings", version)[0] - assert results.families("is_benefit_cap_exempt", version)[0] + assert results.families("is_uc_benefit_cap_exempt_earnings", version)[0] + assert results.families("is_uc_benefit_cap_exempt", version)[0] assert results.families("benefit_cap_reduction", version)[0] == 0 assert ( results.families("universal_credit", HIGH)[0] @@ -323,7 +323,7 @@ def both(variable): before_cap = both("universal_credit_pre_benefit_cap") award = both("universal_credit") childcare = both("uc_childcare_element") - exempt = both("is_benefit_cap_exempt") + exempt = both("is_uc_benefit_cap_exempt") earned = "uc_individual_earned_income_before_mif" for i, family in enumerate(population): if family["low"] > 0: @@ -442,7 +442,7 @@ def test_statutory_pay_counts_as_wages_and_earned_income_adds_up(population): for variable in [ "uc_earned_income", "universal_credit_pre_benefit_cap", - "is_benefit_cap_exempt_earnings", + "is_uc_benefit_cap_exempt_earnings", "universal_credit", ]: taxed = [counts_as_wages(family) for family in population] @@ -567,7 +567,7 @@ def test_award_falls_along_a_path_of_rising_statutory_pay(): variables = [ "universal_credit_pre_benefit_cap", "universal_credit", - "is_benefit_cap_exempt", + "is_uc_benefit_cap_exempt", ] paths = along_paths(list(ARCHETYPES.values()), PAYMENTS, variables) for i, name in enumerate(ARCHETYPES): @@ -580,7 +580,7 @@ def test_award_falls_along_a_path_of_rising_statutory_pay(): # After the cap, the award rises only at a step that makes the # family exempt (reg. 82(1)(a)). final = paths["universal_credit"][HIGH][i, j] - newly_exempt = np.diff(paths["is_benefit_cap_exempt"][HIGH][i, j]) > 0 + newly_exempt = np.diff(paths["is_uc_benefit_cap_exempt"][HIGH][i, j]) > 0 rises = np.diff(final) > TOLERANCE assert not (rises & ~newly_exempt).any(), (name, payment, final) diff --git a/policyengine_uk/utils/benefit_cap.py b/policyengine_uk/utils/benefit_cap.py index 6c9e25989..9a334e3dd 100644 --- a/policyengine_uk/utils/benefit_cap.py +++ b/policyengine_uk/utils/benefit_cap.py @@ -1,19 +1,50 @@ -"""Who the benefit cap reads, for the Universal Credit and Housing Benefit caps.""" +"""The annual limits of the benefit cap, and whose benefits it counts. -from policyengine_core.model_api import where +Universal Credit and Housing Benefit apply the same four annual limits (UC +Regs 2013 reg. 80A(2); HB Regs 2006 reg. 75CA(2)), each scheme deciding for +itself who is a single claimant. Both place a claimant in Greater London by +the home they occupy (reg. 80A(3); reg. 75CA(3)). +""" + +from policyengine_core.model_api import select, where + + +def benefit_cap_annual_limit(benunit, period, parameters, single_claimant_rate): + """The applicable annual limit for the single-claimant or other rate.""" + household_region = benunit.members.household("region", period) + region = benunit.value_from_first_person(household_region) + in_london = region == household_region.possible_values.LONDON + cap = parameters(period).gov.dwp.benefit_cap + return select( + [ + single_claimant_rate & in_london, + single_claimant_rate & ~in_london, + ~single_claimant_rate & in_london, + ~single_claimant_rate & ~in_london, + ], + [ + cap.single.in_london, + cap.single.outside_london, + cap.non_single.in_london, + cap.non_single.outside_london, + ], + ) def benefit_cap_couple(benunit, period): - """The single person or couple whose benefits and circumstances the cap reads. + """The single person or couple whose welfare benefits the cap counts. - The model applies one cap to Universal Credit and Housing Benefit, and - each scheme names its own couple. For a family on Universal Credit it is + Each scheme names its own couple. For a family on Universal Credit it is the claimants, the members of the couple whose income the award assesses (is_uc_assessed_claimant): a single claimant or joint claimants, and the other member of a couple where one member claims as a single person (UC Regs 2013 reg 78(2)). Otherwise it is Housing Benefit's claimant and - partner (is_claimant_or_partner; HB Regs 2006 regs 2(1), 75A). The two - are the same unless is_uc_claimant is entered. + partner (is_claimant_or_partner; HB Regs 2006 regs 2(1), 75A). One total + of welfare benefits (benefit_cap_welfare_benefits) serves both caps: a + family receiving both awards is capped only through Universal Credit (UC + Regs 2013 reg 80(2A); HB Regs 2006 reg 75F(1)(g)), so its total is the + Universal Credit couple's. The two couples are the same unless + is_uc_claimant is entered. """ person = benunit.members on_universal_credit = benunit("universal_credit_pre_benefit_cap", period) > 0 diff --git a/policyengine_uk/utils/benefit_unit.py b/policyengine_uk/utils/benefit_unit.py index b20b7a80b..2938336cb 100644 --- a/policyengine_uk/utils/benefit_unit.py +++ b/policyengine_uk/utils/benefit_unit.py @@ -26,6 +26,29 @@ def add_for_members(benunit, period, variables, members): return total +def has_sixteen_year_old_dependant(benunit, period): + """Whether a member aged 16 who is not the claimant or partner is present. + + Such a member is a dependant for part of the year under either scheme, + whatever their education: Universal Credit makes them a qualifying young + person up to the 1 September after their 16th birthday (UC Regs 2013 + reg. 5(1)(a)), and Housing Benefit takes the Child Benefit qualifying + young person (HB Regs 2006 reg. 19(1)), which a school leaver remains + until a terminal date or the end of an extension period (Child Benefit + (General) Regulations 2006 regs. 5 and 7). Annual ages cannot place + those dates, so the benefit cap rates, which turn on responsibility for a + child or young person, count such a member for the year. The Housing + Benefit cap exceptions read the model's Child Benefit qualifying young + person instead, which leaves out a 16-year-old in neither non-advanced + education nor approved training (#2287). + """ + person = benunit.members + age = person("age", period) + return benunit.any( + (age >= 16) & (age < 17) & ~person("is_claimant_or_partner", period) + ) + + def award_of_members(benunit, period, award, report, award_on_reports, members): """The part of the benefit-unit award ``award`` that ``members`` claim. diff --git a/policyengine_uk/utils/uc_work_related_requirements.py b/policyengine_uk/utils/uc_work_related_requirements.py index 5c58eaa0d..d02dd046a 100644 --- a/policyengine_uk/utils/uc_work_related_requirements.py +++ b/policyengine_uk/utils/uc_work_related_requirements.py @@ -32,38 +32,3 @@ def other_member_of_single_claim(person, period): return person("uc_is_ineligible_partner", period) & person.benunit( "uc_member_of_couple_claims_as_single_person", period ) - - -def stays_on_legacy_benefits(benunit, period): - """Whether the family stays on legacy benefits rather than claiming UC. - - A family that reports a legacy benefit (`claims_legacy_benefits`) and - does not claim Universal Credit keeps its legacy awards; this is the - route `housing_benefit_eligible` gives a continuing working-age Housing - Benefit award before its legacy benefits close. A family claiming - Universal Credit, including at a legacy closure, uses the UC rules. - """ - return benunit("claims_legacy_benefits", period) & ~benunit( - "claims_universal_credit", period - ) - - -def single_claim_in_rules_shared_with_legacy_benefits(benunit, period): - """Whether a reg. 3(3) single claim sets the rules the model shares. - - The benefit cap rate and exceptions and the LHA shared accommodation - test serve Housing Benefit as well as Universal Credit. Housing Benefit - and the other legacy benefits have no single claim by a member of a - couple, so a family that stays on legacy benefits keeps the couple rules; - a family claiming Universal Credit gets the Universal Credit rule. - """ - return benunit( - "uc_member_of_couple_claims_as_single_person", period - ) & ~stays_on_legacy_benefits(benunit, period) - - -def other_member_of_single_claim_in_shared_rules(person, period): - """`other_member_of_single_claim`, for the rules shared with legacy benefits.""" - return other_member_of_single_claim(person, period) & ~stays_on_legacy_benefits( - person.benunit, period - ) diff --git a/policyengine_uk/variables/gov/dwp/benefit_cap.py b/policyengine_uk/variables/gov/dwp/benefit_cap.py deleted file mode 100644 index f67e21804..000000000 --- a/policyengine_uk/variables/gov/dwp/benefit_cap.py +++ /dev/null @@ -1,33 +0,0 @@ -from policyengine_uk.model_api import * - - -class benefit_cap(Variable): - value_type = float - entity = BenUnit - label = "Benefit cap for the family" - definition_period = YEAR - unit = GBP - - def formula(benunit, period, parameters): - single_claimant = benunit("is_benefit_cap_single_claimant_rate", period) - household_region = benunit.members.household("region", period) - region = benunit.value_from_first_person(household_region) - regions = household_region.possible_values - in_london = region == regions.LONDON - cap = parameters(period).gov.dwp.benefit_cap - rate = select( - [ - single_claimant & in_london, - single_claimant & ~in_london, - ~single_claimant & in_london, - ~single_claimant & ~in_london, - ], - [ - cap.single.in_london, - cap.single.outside_london, - cap.non_single.in_london, - cap.non_single.outside_london, - ], - ) - exempt = benunit("is_benefit_cap_exempt", period) - return where(exempt, np.inf, rate) diff --git a/policyengine_uk/variables/gov/dwp/benefit_cap_reduction.py b/policyengine_uk/variables/gov/dwp/benefit_cap_reduction.py index e67feb233..31d051753 100644 --- a/policyengine_uk/variables/gov/dwp/benefit_cap_reduction.py +++ b/policyengine_uk/variables/gov/dwp/benefit_cap_reduction.py @@ -1,79 +1,23 @@ from policyengine_uk.model_api import * -from policyengine_uk.utils.benefit_cap import benefit_cap_couple -from policyengine_uk.utils.benefit_unit import award_of_members -from policyengine_uk.variables.gov.dwp.esa_income import income_related_esa_award -from policyengine_uk.variables.gov.dwp.jsa_income import income_related_jsa_award - -# Welfare benefits (Welfare Reform Act 2012 s.96(10)) the model has. The cap -# counts those "to which the single person or couple is entitled" (UC Regs -# 2013 regs 79(1), 80(1); HB Regs 2006 reg 75A): the claimant's and partner's -# own awards, and the awards made to the family. Another member of the -# benefit unit, such as a non-dependent adult, is entitled to their own -# contributory or income-related benefit; it still counts in household income. -CLAIMANT_OR_PARTNER_BENEFITS = [ - "jsa_contrib", - "esa_contrib", - "incapacity_benefit", - "sda", -] -FAMILY_BENEFITS = [ - "child_benefit", - "child_tax_credit", - "income_support", - "universal_credit_pre_benefit_cap", - "housing_benefit_pre_benefit_cap", -] class benefit_cap_reduction(Variable): label = "benefit cap reduction" documentation = ( - "The amount by which the welfare benefits the claimant or couple is " - "entitled to exceed the benefit cap. For a family on Universal Credit " - "the couple is its claimants (is_uc_assessed_claimant), including the " - "other member of a couple where one member claims as a single person; " - "otherwise it is the claimant and partner (is_claimant_or_partner). " - "The two are the same unless is_uc_claimant is entered. Benefits " - "another member of the benefit unit claims in their own right do not " - "count. Maternity allowance and the bereavement benefits, also welfare " - "benefits, are not modelled here." + "The reduction the benefit cap makes to the family's Universal Credit " + "(uc_benefit_cap_reduction, UC Regs 2013 reg. 81) or Housing Benefit " + "(housing_benefit_benefit_cap_reduction, HB Regs 2006 reg. 75D). Each " + "scheme applies its own cap. A family receiving both is capped only " + "through Universal Credit: Universal Credit counts its Housing Benefit " + "as nil (UC Regs 2013 reg. 80(2A)) and the Housing Benefit cap does " + "not apply to a claimant receiving Universal Credit (HB Regs 2006 " + "reg. 75F(1)(g)). A family with neither award has no reduction. The " + "Universal Credit part is the reg. 81 amount, which can exceed the " + "award; the Housing Benefit part never exceeds the award less the " + "reg. 75 minimum." ) entity = BenUnit definition_period = YEAR value_type = float unit = GBP - reference = ( - "https://www.legislation.gov.uk/ukpga/2012/5/section/96", - "https://www.legislation.gov.uk/uksi/2013/376/regulation/78", - "https://www.legislation.gov.uk/uksi/2013/376/regulation/80", - "https://www.legislation.gov.uk/uksi/2006/213/regulation/75A", - ) - - def formula(benunit, period, parameters): - # The couple of the scheme the cap reduces (benefit_cap_couple). - couple = benefit_cap_couple(benunit, period) - # Income-based JSA and income-related ESA are awarded to the benefit - # unit; the couple's part is read from their reports, as in - # claimant_or_partner_jsa_income and claimant_or_partner_esa_income. - income_based = award_of_members( - benunit, - period, - "jsa_income", - "jsa_income_reported", - income_related_jsa_award, - couple, - ) + award_of_members( - benunit, - period, - "esa_income", - "esa_income_reported", - income_related_esa_award, - couple, - ) - capped = income_based + add_for_members( - benunit, - period, - CLAIMANT_OR_PARTNER_BENEFITS + FAMILY_BENEFITS, - couple, - ) - return max_(capped - benunit("benefit_cap", period), 0) + adds = ["uc_benefit_cap_reduction", "housing_benefit_benefit_cap_reduction"] diff --git a/policyengine_uk/variables/gov/dwp/benefit_cap_welfare_benefits.py b/policyengine_uk/variables/gov/dwp/benefit_cap_welfare_benefits.py new file mode 100644 index 000000000..b17e0c664 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/benefit_cap_welfare_benefits.py @@ -0,0 +1,97 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.benefit_cap import benefit_cap_couple +from policyengine_uk.utils.benefit_unit import award_of_members +from policyengine_uk.variables.gov.dwp.esa_income import income_related_esa_award +from policyengine_uk.variables.gov.dwp.jsa_income import income_related_jsa_award + +# Welfare benefits (Welfare Reform Act 2012 s. 96(10)) the benefit cap counts. +# The cap counts those "to which the single person or couple is entitled" (UC +# Regs 2013 regs 79(1), 80(1); HB Regs 2006 reg 75A): the claimant's and +# partner's own awards, and the awards made to the family. Another member of +# the benefit unit, such as a non-dependent adult, is entitled to their own +# contributory or income-related benefit; it still counts in household income. +# Housing Benefit counts its own amount before the cap (HB Regs 2006 +# reg. 75C(2)(b)), so this total includes it. Universal Credit counts Housing +# Benefit as nil (UC Regs 2013 reg. 80(2A)): uc_benefit_cap_reduction takes it +# out. Universal Credit before the cap is in the total too; a family receiving +# it is outside the Housing Benefit cap (HB Regs 2006 reg. 75F(1)(g)). With +# those two adjustments one total serves both schemes. Reg. 75C(2)(a) counts +# Housing Benefit for specified accommodation as nil; the model's input +# in_specified_or_temporary_accommodation does not separate specified from +# temporary accommodation, so that Housing Benefit is counted at its amount +# before the cap. +CLAIMANT_OR_PARTNER_BENEFITS = [ + "jsa_contrib", + "esa_contrib", + "incapacity_benefit", + "sda", +] +FAMILY_BENEFITS = [ + "child_benefit", + "child_tax_credit", + "income_support", + "universal_credit_pre_benefit_cap", + "housing_benefit_pre_benefit_cap", +] + + +class benefit_cap_welfare_benefits(Variable): + value_type = float + entity = BenUnit + label = "Welfare benefits counted by the benefit cap" + documentation = ( + "The total of the welfare benefits the benefit cap compares with the " + "Universal Credit or Housing Benefit cap (UC Regs 2013 regs. 79(1) " + "and 80; HB Regs 2006 regs. 75A and 75C): those the claimant or " + "couple is entitled to. For a family on Universal Credit the couple " + "is its claimants (is_uc_assessed_claimant), including the other " + "member of a couple where one member claims as a single person; " + "otherwise it is the claimant and partner (is_claimant_or_partner). " + "The two are the same unless is_uc_claimant is entered. Benefits " + "another member of the benefit unit claims in their own right do not " + "count. The total includes Universal Credit and Housing Benefit " + "before the cap (universal_credit_pre_benefit_cap, " + "housing_benefit_pre_benefit_cap). Universal Credit counts Housing " + "Benefit as nil (UC Regs 2013 reg. 80(2A)), so " + "uc_benefit_cap_reduction subtracts it; an entered total should " + "include it. Maternity allowance and the bereavement benefits, also " + "welfare benefits, are not modelled here." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/ukpga/2012/5/section/96", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/78", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/80", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75A", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75C", + ) + + def formula(benunit, period, parameters): + # The couple of the scheme whose award the family has + # (benefit_cap_couple). + couple = benefit_cap_couple(benunit, period) + # Income-based JSA and income-related ESA are awarded to the benefit + # unit; the couple's part is read from their reports, as in + # claimant_or_partner_jsa_income and claimant_or_partner_esa_income. + income_based = award_of_members( + benunit, + period, + "jsa_income", + "jsa_income_reported", + income_related_jsa_award, + couple, + ) + award_of_members( + benunit, + period, + "esa_income", + "esa_income_reported", + income_related_esa_award, + couple, + ) + return income_based + add_for_members( + benunit, + period, + CLAIMANT_OR_PARTNER_BENEFITS + FAMILY_BENEFITS, + couple, + ) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/housing_benefit_benefit_cap.py b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/housing_benefit_benefit_cap.py new file mode 100644 index 000000000..bc00f3d42 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/housing_benefit_benefit_cap.py @@ -0,0 +1,33 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.benefit_cap import benefit_cap_annual_limit + + +class housing_benefit_benefit_cap(Variable): + value_type = float + entity = BenUnit + label = "Housing Benefit benefit cap" + documentation = ( + "The applicable annual limit on the welfare benefits of a Housing " + "Benefit claimant or couple (HB Regs 2006 reg. 75CA(2)), or infinity " + "where the cap does not apply: an exception in regulation 75E or 75F " + "(reg. 75A), or a claim under the pension-age regulations, which have " + "no benefit cap. The weekly relevant amount (the annual limit divided " + "by 52, rounded to the nearest penny: reg. 75CA(1)) is not rounded: " + "the model applies the annual limit." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75A", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75CA", + ) + + def formula(benunit, period, parameters): + limit = benefit_cap_annual_limit( + benunit, + period, + parameters, + benunit("is_housing_benefit_benefit_cap_single_claimant_rate", period), + ) + exempt = benunit("is_housing_benefit_benefit_cap_exempt", period) + return where(exempt, np.inf, limit) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/housing_benefit_benefit_cap_reduction.py b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/housing_benefit_benefit_cap_reduction.py new file mode 100644 index 000000000..a145d92c7 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/housing_benefit_benefit_cap_reduction.py @@ -0,0 +1,33 @@ +from policyengine_uk.model_api import * + + +class housing_benefit_benefit_cap_reduction(Variable): + value_type = float + entity = BenUnit + label = "Housing Benefit benefit cap reduction" + documentation = ( + "The reduction the benefit cap makes to Housing Benefit: the excess " + "of the welfare benefits of the claimant or couple over the Housing " + "Benefit cap, limited so that the claimant keeps the minimum amount " + "of Housing Benefit, 50 pence a week (HB Regs 2006 regs. 75 and " + "75D)." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75D", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75", + ) + + def formula(benunit, period, parameters): + excess = max_( + benunit("benefit_cap_welfare_benefits", period) + - benunit("housing_benefit_benefit_cap", period), + 0, + ) + entitlement = benunit("housing_benefit_pre_benefit_cap", period) + minimum = ( + parameters(period).gov.dwp.housing_benefit.minimum_weekly_amount + * WEEKS_IN_YEAR + ) + return min_(excess, max_(entitlement - minimum, 0)) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt.py b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt.py new file mode 100644 index 000000000..38b0f8656 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt.py @@ -0,0 +1,30 @@ +from policyengine_uk.model_api import * + + +class is_housing_benefit_benefit_cap_exempt(Variable): + value_type = bool + entity = BenUnit + label = "Exempt from the Housing Benefit benefit cap" + documentation = ( + "Whether the benefit cap does not apply to the family's Housing " + "Benefit: regulation 75E (working tax credit) or 75F (specified " + "benefits) of the HB Regs 2006 applies (reg. 75A), or the claim falls " + "under the Housing Benefit (Persons who have attained the qualifying " + "age for state pension credit) Regulations 2006 (reg. 5), which have " + "no benefit cap. Housing Benefit has no earnings exception and no " + "exception for the Universal Credit LCWRA or carer elements." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75A", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75E", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75F", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/5", + ) + + def formula(benunit, period, parameters): + return ( + benunit("is_housing_benefit_benefit_cap_exempt_working_tax_credit", period) + | benunit("is_housing_benefit_benefit_cap_exempt_specified_benefit", period) + | benunit("housing_benefit_pension_age_regulations_apply", period) + ) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_specified_benefit.py b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_specified_benefit.py new file mode 100644 index 000000000..08bb32bb1 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_specified_benefit.py @@ -0,0 +1,155 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.benefit_unit import award_of_members +from policyengine_uk.variables.gov.dwp._legacy_award_payee import ( + is_payee_of_couple_award, +) +from policyengine_uk.variables.gov.dwp.esa_income import income_related_esa_award + +# HB Regs 2006 reg 75F(1) names, for each benefit, whose receipt (or +# entitlement) lifts the cap. + +# "the claimant or the claimant's partner is receiving" (reg 75F(1)(b)-(d)): +# industrial injuries benefit, attendance allowance, and a war pension, which +# includes the Armed Forces Compensation Scheme's guaranteed income payments +# (reg 75F(2)(a)). +CLAIMANT_OR_PARTNER_BENEFITS = [ + "attendance_allowance", + "iidb", + "afcs", +] + +# "the claimant, the claimant's partner or a child or young person for whom +# the claimant or the claimant's partner is responsible" (reg 75F(1)(e)). +CHILD_OR_YOUNG_PERSON_BENEFITS = [ + "dla", +] + +# "the claimant, the claimant's partner or a young person for whom the +# claimant or the claimant's partner is responsible" (reg 75F(1)(ea), (h), +# (ha)): personal independence payment, armed forces independence payment, +# and entitlement to carer's allowance or carer support payment. These count +# by entitlement, not payment: (h)-(ha) name a person "entitled to" them, even +# if an overlapping benefit reduces the payment to nil. +YOUNG_PERSON_BENEFITS = [ + "pip", + "armed_forces_independence_payment", + "is_entitled_to_carer_benefit", +] + + +class is_housing_benefit_benefit_cap_exempt_specified_benefit(Variable): + value_type = bool + entity = BenUnit + label = "Exempt from the Housing Benefit benefit cap through a specified benefit" + documentation = ( + "Whether the claimant, partner or (for some benefits) a child or " + "young person they are responsible for receives or is entitled to a " + "benefit listed in HB Regs 2006 reg. 75F(1): employment and support " + "allowance with the support component, industrial injuries benefit, " + "attendance allowance, a war pension or armed forces compensation, " + "disability living allowance, personal independence payment, armed " + "forces independence payment, or entitlement to carer's allowance or " + "carer support payment; or a Housing Benefit claimant is receiving " + "Universal Credit (reg. 75F(1)(g)), whose own cap applies instead. " + "Housing Benefit has no claim by a member of a couple as a single " + "person, so the partner's benefits always count. Another member of " + "the benefit unit, such as a non-dependent adult, does not lift the " + "cap with their own benefit. The Universal Credit LCWRA and carer " + "elements are not Housing Benefit exceptions. Regulation 75F(1)(g) " + "reads a family with Housing Benefit before the cap and an award of " + "Universal Credit before the cap (universal_credit_pre_benefit_cap), " + "so a Universal Credit award the cap reduces to nil still lifts the " + "Housing Benefit cap. A young person is the model's Child Benefit " + "qualifying young person, which leaves out a 16-year-old in neither " + "non-advanced education nor approved training. The Housing Benefit " + "cap rate (is_housing_benefit_benefit_cap_single_claimant_rate) " + "counts such a 16-year-old for the year, so a lone parent with one " + "gets the family rate, but the 16-year-old's own disability living " + "allowance or personal independence payment does not lift the cap. " + "In law one status decides both: a school leaver remains a " + "qualifying young person until a terminal date or the end of an " + "extension period (Child Benefit (General) Regulations 2006 regs. 5 " + "and 7), which the model does not place (#2287)." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75F", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/2", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/19", + "https://www.gov.uk/benefit-cap/when-youre-not-affected", + ) + + def formula(benunit, period, parameters): + person = benunit.members + # The claimant and partner (HB Regs 2006 reg 2(1)). + claimant_or_partner = person("is_claimant_or_partner", period) + + # A child under 16 or a Child Benefit qualifying young person the + # claimant or partner is responsible for (HB Regs 2006 regs 2(1), 19). + # A 16-year-old in neither non-advanced education nor approved + # training is not one here, although the cap rate counts them for the + # year (has_sixteen_year_old_dependant); the model's qualifying young + # person has no school-leaver terminal date (#2287). + child_or_young_person = person( + "is_child_or_young_person_for_legacy_benefits", period + ) + young_person = child_or_young_person & person( + "is_qualifying_young_person_for_child_benefit", period + ) + + def received_by(benefits, members): + return add_for_members(benunit, period, benefits, members) > 0 + + # Reg 75F(1)(a): "the claimant or the claimant's partner is receiving + # an employment and support allowance ... which includes a support + # component". Contributory and income-related allowances both carry + # it; another member's allowance does not count. The couple's + # income-related award is the part of the family's award on the + # couple's reports, as in claimant_or_partner_esa_income, and it is + # received by its payee (reg 2(3A); is_on_income_related_esa reads + # the same). + couple_esa_award = award_of_members( + benunit, + period, + "esa_income", + "esa_income_reported", + income_related_esa_award, + claimant_or_partner, + ) + receiving_income_related_esa = benunit.project( + couple_esa_award > 0 + ) & is_payee_of_couple_award( + person, period, "esa_income_reported", couple=claimant_or_partner + ) + receiving_esa = (person("esa_contrib", period) > 0) | ( + receiving_income_related_esa + ) + esa_support_component = benunit.any( + claimant_or_partner + & receiving_esa + & person("esa_includes_support_component", period) + ) + + # Reg 75F(1)(g): "the claimant is receiving universal credit". The + # claimant is a Housing Benefit claimant, a family with Housing + # Benefit before the cap; it is then capped through Universal Credit, + # which counts its Housing Benefit as nil (UC Regs 2013 reg 80(2A)). + # A family on Universal Credit alone keeps the exceptions above, so + # the two schemes' exceptions can be compared for it. Housing Benefit + # eligibility (housing_benefit_eligible) rules out a family that + # claims Universal Credit, so this applies only where both awards are + # supplied. + receiving_universal_credit = ( + benunit("universal_credit_pre_benefit_cap", period) > 0 + ) & (benunit("housing_benefit_pre_benefit_cap", period) > 0) + + return ( + receiving_universal_credit + | esa_support_component + | received_by(CLAIMANT_OR_PARTNER_BENEFITS, claimant_or_partner) + | received_by( + CHILD_OR_YOUNG_PERSON_BENEFITS, + claimant_or_partner | child_or_young_person, + ) + | received_by(YOUNG_PERSON_BENEFITS, claimant_or_partner | young_person) + ) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_working_tax_credit.py b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_working_tax_credit.py new file mode 100644 index 000000000..ce55bfb42 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_working_tax_credit.py @@ -0,0 +1,18 @@ +from policyengine_uk.model_api import * + + +class is_housing_benefit_benefit_cap_exempt_working_tax_credit(Variable): + value_type = bool + entity = BenUnit + label = "Exempt from the Housing Benefit benefit cap through working tax credit" + documentation = ( + "Whether the claimant is, or the claimant and partner are jointly, " + "entitled to working tax credit (HB Regs 2006 reg. 75E(2)). The " + "39-week grace period after work ends (reg. 75E(3)-(5)) is not " + "modelled." + ) + definition_period = YEAR + reference = "https://www.legislation.gov.uk/uksi/2006/213/regulation/75E" + + def formula(benunit, period, parameters): + return benunit("working_tax_credit", period) > 0 diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_single_claimant_rate.py b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_single_claimant_rate.py new file mode 100644 index 000000000..9d5daa2c2 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_single_claimant_rate.py @@ -0,0 +1,34 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.benefit_unit import has_sixteen_year_old_dependant + + +class is_housing_benefit_benefit_cap_single_claimant_rate(Variable): + value_type = bool + entity = BenUnit + label = "Housing Benefit benefit cap single claimant rate applies" + documentation = ( + "Whether the Housing Benefit claimant is a single claimant (HB Regs " + "2006 reg. 75CA(2)(a) and (c)): a claimant who neither has a partner " + "nor is a lone parent, that is, responsible for a child or young " + "person in the same household (reg. 2(1)). Housing Benefit has no " + "claim by a member of a couple as a single person, so a couple has " + "the other rate even where the partner could not be a joint claimant " + "for Universal Credit. A 16-year-old member counts as a young person " + "for the year (reg. 19(1); Child Benefit (General) Regulations 2006 " + "regs. 5 and 7). The exceptions " + "(is_housing_benefit_benefit_cap_exempt_specified_benefit) read the " + "model's Child Benefit qualifying young person instead, which leaves " + "out a 16-year-old in neither non-advanced education nor approved " + "training (#2287)." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/75CA", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/2", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/19", + ) + + def formula(benunit, period, parameters): + return benunit("is_single_person", period) & ~has_sixteen_year_old_dependant( + benunit, period + ) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit.py index 2b02130f7..088ecbf5b 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit.py @@ -11,5 +11,5 @@ class housing_benefit(Variable): def formula(benunit, period, parameters): housing_benefit_entitlement = benunit("housing_benefit_pre_benefit_cap", period) - benefit_cap_reduction = benunit("benefit_cap_reduction", period) + benefit_cap_reduction = benunit("housing_benefit_benefit_cap_reduction", period) return max_(0, housing_benefit_entitlement - benefit_cap_reduction) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_on_passporting_benefit.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_on_passporting_benefit.py index 42bc4575e..780d17bc5 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_on_passporting_benefit.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_on_passporting_benefit.py @@ -56,7 +56,8 @@ def formula(benunit, period, parameters): ) # Universal Credit before the benefit cap and deductions: Universal # Credit depends on Housing Benefit through the benefit cap - # (benefit_cap_reduction), so reading the amount paid + # (uc_benefit_cap_reduction, whose total benefit_cap_welfare_benefits + # counts Housing Benefit), so reading the amount paid # (universal_credit) here would be circular. Before the cap is also # the law's reading: entitlement counts whether or not it is paid. on_universal_credit = benunit("is_uc_entitled", period) & universal_credit_limb diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py index c30929212..821c16fef 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_pension_age_regulations_apply.py @@ -16,15 +16,16 @@ class housing_benefit_pension_age_regulations_apply(Variable): "benefit cap (Part 8A of the working-age regulations) does not reach " "Housing Benefit under the pension-age regulations. A Universal Credit " "award counts only where a claimant or partner is under the qualifying " - "age. That is right for the cap, which SI 2014/1230 reg 60C disapplies " - "for a claim where every claimant has reached that age, but not for " - "reg 5 itself, under which such an award would bring in the " - "working-age regulations. The model's only such awards go to tax " + "age. That is not right for reg 5 itself, under which such an award " + "would bring in the working-age regulations. The model's only such " + "awards go to tax " "credit migrants under SI 2014/1230 reg 60A (claims_uc_at_legacy_" "closure). While entitled, they leave the pension-age route and get " "no Housing Benefit under either set of regulations; a claim with " - "nil entitlement keeps that route. The cap does not apply to their " - "Universal Credit awards (reg 60C)." + "nil entitlement keeps that route. Their Universal Credit awards are " + "not capped (SI 2014/1230 reg 60C), through Universal Credit's own " + "exception (is_uc_benefit_cap_exempt_qualifying_age), not this " + "variable, which the Housing Benefit cap reads (HB Regs 2006 reg 5)." ) definition_period = YEAR reference = ( diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt.py deleted file mode 100644 index a0303a525..000000000 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt.py +++ /dev/null @@ -1,15 +0,0 @@ -from policyengine_uk.model_api import * - - -class is_benefit_cap_exempt(Variable): - value_type = bool - entity = BenUnit - label = "Exempt from the benefit cap" - definition_period = YEAR - reference = "https://www.gov.uk/benefit-cap/when-youre-not-affected" - - def formula(benunit, period, parameters): - exempt_health = benunit("is_benefit_cap_exempt_health_disability", period) - exempt_other = benunit("is_benefit_cap_exempt_other", period) - exempt_earnings = benunit("is_benefit_cap_exempt_earnings", period) - return exempt_health | exempt_earnings | exempt_other diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py deleted file mode 100644 index 391673b3d..000000000 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py +++ /dev/null @@ -1,254 +0,0 @@ -import os - -from policyengine_uk.model_api import * -from policyengine_uk.utils.benefit_cap import benefit_cap_couple -from policyengine_uk.utils.benefit_unit import award_of_members -from policyengine_uk.utils.supplied_inputs import supplied_input -from policyengine_uk.utils.uc_work_related_requirements import ( - other_member_of_single_claim_in_shared_rules, -) -from policyengine_uk.variables.gov.dwp._legacy_award_payee import ( - is_payee_of_couple_award, -) -from policyengine_uk.variables.gov.dwp.esa_income import income_related_esa_award -from policyengine_uk.variables.gov.dwp.universal_credit.carer_element.uc_carer_element import ( - uc_carer_element, -) -from policyengine_uk.variables.gov.dwp.universal_credit.disability_element.limited_work_ability.uc_LCWRA_element import ( - uc_LCWRA_element, -) - -# HB Regs 2006 reg 75F(1) and UC Regs 2013 reg 83(1) name, for each benefit, -# whose receipt (or entitlement) lifts the cap. - -# "the claimant or the claimant's partner is receiving" (HB reg 75F(1)(b)-(d)); -# "a claimant is receiving" (UC reg 83(1)(b)-(e)). Armed Forces Compensation -# Scheme payments are the guaranteed income payments that count as a war -# pension (HB reg 75F(2)(a); UC reg 83(1)(e)). -CLAIMANT_OR_PARTNER_BENEFITS = [ - "attendance_allowance", - "iidb", - "afcs", -] - -# "the claimant, the claimant's partner or a child or young person for whom -# the claimant or the claimant's partner is responsible" (HB reg 75F(1)(e)); -# "a claimant, or a child or qualifying young person for whom a claimant is -# responsible" (UC reg 83(1)(f)). -CHILD_OR_YOUNG_PERSON_BENEFITS = [ - "dla", -] - -# "the claimant, the claimant's partner or a young person for whom the -# claimant or the claimant's partner is responsible" (HB reg 75F(1)(ea), (h), -# (ha)); "a claimant, or a qualifying young person for whom a claimant is -# responsible" (UC reg 83(1)(g), (i), (ia)). Carer's allowance and carer -# support payment count by entitlement, not payment: (h)-(ha) and (i)-(ia) -# name a person "entitled to" them, even if an overlapping benefit reduces -# the payment to nil. -YOUNG_PERSON_BENEFITS = [ - "pip", - "is_entitled_to_carer_benefit", -] - -# Armed forces independence payment: Housing Benefit lists it with personal -# independence payment, for a young person too (HB reg 75F(1)(ea)); Universal -# Credit counts it as attendance allowance, a claimant's only (UC Regs 2013 -# reg 2, reg 83(1)(c)). Either scheme: the claimant, partner or a Housing -# Benefit young person. -HOUSING_BENEFIT_YOUNG_PERSON_BENEFITS = [ - "armed_forces_independence_payment", -] - - -def calculated_by(population, variable_name, period, model_variable): - """Whether the simulation calculates ``variable_name`` with the model's own - formula (``model_variable``'s), rather than one a reform put in its place. - The tax-benefit system loads each variable file as its own module, so the - formula is matched by where its code is, not by identity.""" - variable = population.simulation.tax_benefit_system.get_variable(variable_name) - formula = variable.get_formula(period) - code = getattr(formula, "__code__", None) - model = model_variable.formula.__code__ - return code is not None and ( - os.path.realpath(code.co_filename), - code.co_firstlineno, - ) == (os.path.realpath(model.co_filename), model.co_firstlineno) - - -class is_benefit_cap_exempt_health_disability(Variable): - value_type = bool - entity = BenUnit - label = "Exempt from the benefit cap through a specified benefit, UC element or working tax credit" - documentation = ( - "Whether the family is outside the benefit cap because the claimant, " - "their partner or, for some benefits, a child or young person they " - "are responsible for receives or is entitled to a benefit listed in " - "HB Regs 2006 reg 75F(1) or UC Regs 2013 reg 83(1), or because the " - "claimant or couple is entitled to working tax credit (HB Regs 2006 " - "reg 75E(2)). Another member of the benefit unit, such as a " - "non-dependent adult, does not lift the cap with their own benefit, " - "and nor does a partner who cannot be a joint claimant, so that the " - "other member claims Universal Credit as a single person (UC Regs " - "2013 reg 3(3)). For a family on Universal Credit the claimant and " - "partner are the award's claimants, otherwise Housing Benefit's " - "claimant and partner (benefit_cap_couple). " - "The model applies one cap to Universal Credit and Housing Benefit, so " - "an exception in either scheme counts: Universal Credit's LCWRA and " - "carer elements, and Housing Benefit's wider young-person tests." - ) - definition_period = YEAR - reference = ( - "https://www.legislation.gov.uk/uksi/2006/213/regulation/75F", - "https://www.legislation.gov.uk/uksi/2006/213/regulation/75E", - "https://www.legislation.gov.uk/uksi/2013/376/regulation/83", - "https://www.legislation.gov.uk/uksi/2013/376/regulation/3", - "https://www.gov.uk/benefit-cap/when-youre-not-affected", - ) - - def formula(benunit, period, parameters): - person = benunit.members - # The couple of the scheme the cap reduces (benefit_cap_couple): the - # claimants of a Universal Credit award, or Housing Benefit's - # claimant and partner (HB Regs 2006 reg 2(1)). - claimant_or_partner = benefit_cap_couple(benunit, period) - # UC reg 83(1) reads "a claimant". A partner who cannot be a joint - # claimant, so that the other member claims Universal Credit as a - # single person (reg 3(3)), is a member of the couple but not a - # claimant: their own benefits and limited capability do not lift the - # cap. Housing Benefit has no such claim, so a family that stays on - # legacy benefits keeps both members. - claimant = claimant_or_partner & ~other_member_of_single_claim_in_shared_rules( - person, period - ) - - # A child or young person the claimant or partner is responsible for. - # Housing Benefit counts a child under 16 or a Child Benefit - # qualifying young person (HB Regs 2006 regs 2(1), 19); Universal - # Credit a child or qualifying young person (UC Regs 2013 regs 4-5), - # which includes every 16-year-old until the 1 September after their - # birthday (reg 5(1)(a)), unless they receive Universal Credit, ESA - # or JSA themselves (reg 5(5)). Annual ages cannot place that date, so - # any such 16-year-old counts, as for the family rate in - # is_responsible_for_child_or_young_person_for_uc_or_housing_benefit - # (which does not yet apply reg 5(5)). - age = person("age", period) - legacy_child_or_young_person = person( - "is_child_or_young_person_for_legacy_benefits", period - ) - housing_benefit_young_person = legacy_child_or_young_person & person( - "is_qualifying_young_person_for_child_benefit", period - ) - uc_child_or_young_person = ~claimant_or_partner & person( - "is_child_or_qualifying_young_person_for_universal_credit", period - ) - uc_sixteen = ( - (age >= 16) - & (age < 17) - & ~claimant_or_partner - & ~person("is_looked_after_by_local_authority", period) - & ~person("receives_benefits_in_own_right", period) - ) - child_or_young_person = ( - legacy_child_or_young_person | uc_child_or_young_person | uc_sixteen - ) - young_person = ( - housing_benefit_young_person - | ( - uc_child_or_young_person - & person("is_qualifying_young_person_for_universal_credit", period) - ) - | uc_sixteen - ) - - def received_by(benefits, members): - return add_for_members(benunit, period, benefits, members) > 0 - - # HB reg 75F(1)(a), UC reg 83(1)(a): "the claimant or the claimant's - # partner is receiving an employment and support allowance ... which - # includes a support component"; UC reg 83(1)(a) "the claimant". - # Contributory and income-related allowances both carry it; another - # member's allowance does not count. The couple's income-related - # award is the part of the family's award on the couple's reports, as - # in claimant_or_partner_esa_income, and it is received by its payee - # (HB Regs 2006 reg 2(3A); is_on_income_related_esa reads the same for - # Housing Benefit's claimant and partner). - couple_esa_award = award_of_members( - benunit, - period, - "esa_income", - "esa_income_reported", - income_related_esa_award, - claimant_or_partner, - ) - receiving_income_related_esa = benunit.project( - couple_esa_award > 0 - ) & is_payee_of_couple_award( - person, period, "esa_income_reported", couple=claimant_or_partner - ) - receiving_esa = (person("esa_contrib", period) > 0) | ( - receiving_income_related_esa - ) - esa_support_component = benunit.any( - claimant & receiving_esa & person("esa_includes_support_component", period) - ) - - # UC reg 83(1)(a), (j): the LCWRA or carer element is included in the - # award. An element entered for the award is included exactly when it - # is positive, whatever the members' circumstances (an entry of nil - # means the award does not include it). An element a reform's formula - # gives is included when positive. The model's own formula still - # gives the element for any member's limited capability for work and - # work-related activity or caring responsibilities, but the element is - # for "a claimant" (UC Regs 2013 regs 27(1), 29(1)). So the element it - # gives is included when positive unless the only members with the - # circumstance are not claimants. A circumstance entered for the - # family alone (benunit_has_carer) names no member, and a nil amount - # (benunit_has_carer entered as false, or a nil rate) is not included. - # The Universal Credit rebalancing rescales the formula's elements - # from 2026 (scenarios/uc_reform.py) and is read the same way. The - # model does not have the reg 28 waiting period or regs 29(5)-(6), - # 30(3). Where reg 29(4) leaves a carer with limited capability only - # the LCWRA element, that element exempts. - def element_included(element, model_variable, circumstance): - entered = supplied_input(benunit, element, period) - if entered is not None: - return entered > 0 - positive = benunit(element, period) > 0 - if not calculated_by(benunit, element, period, model_variable): - return positive - only_other_members = benunit.any(~claimant & circumstance) & ~benunit.any( - claimant & circumstance - ) - return positive & ~only_other_members - - lcwra_element = element_included( - "uc_LCWRA_element", - uc_LCWRA_element, - person("uc_limited_capability_for_WRA", period), - ) - carer_element = element_included( - "uc_carer_element", - uc_carer_element, - person("is_carer_for_benefits", period), - ) - - # HB reg 75E(2): the claimant is, or the couple are jointly, entitled - # to working tax credit. - working_tax_credit = benunit("working_tax_credit", period) > 0 - - return ( - esa_support_component - | received_by(CLAIMANT_OR_PARTNER_BENEFITS, claimant) - | received_by( - CHILD_OR_YOUNG_PERSON_BENEFITS, claimant | child_or_young_person - ) - | received_by(YOUNG_PERSON_BENEFITS, claimant | young_person) - | received_by( - HOUSING_BENEFIT_YOUNG_PERSON_BENEFITS, - claimant | housing_benefit_young_person, - ) - | lcwra_element - | carer_element - | working_tax_credit - ) diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py deleted file mode 100644 index a15f25e9f..000000000 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py +++ /dev/null @@ -1,38 +0,0 @@ -from policyengine_uk.model_api import * - - -class is_benefit_cap_exempt_other(Variable): - value_type = bool - entity = BenUnit - label = "Exempt from the benefit cap because of age" - documentation = ( - "Whether the family's Housing Benefit falls under the pension-age " - "regulations (HB Regs 2006 reg 5), which have no benefit cap. " - "Universal Credit has no age exception apart from SI 2014/1230 reg " - "60C, for a claim where every claimant has reached the qualifying age " - "for State Pension Credit. " - "The armed forces compensation and support-component ESA exceptions " - "are in is_benefit_cap_exempt_health_disability, which limits them " - "to the claimant and partner." - ) - definition_period = YEAR - reference = ( - "https://www.gov.uk/benefit-cap/when-youre-not-affected", - "https://www.legislation.gov.uk/uksi/2013/376/regulation/79", - "https://www.legislation.gov.uk/uksi/2013/376/regulation/83", - "https://www.legislation.gov.uk/uksi/2014/1230/regulation/60C", - "https://www.legislation.gov.uk/uksi/2006/213/regulation/75A", - "https://www.legislation.gov.uk/uksi/2006/213/regulation/5", - ) - - def formula(benunit, period, parameters): - # The benefit cap applies to a Universal Credit award whatever the - # claimants' ages (UC Regs 2013 regs 79, 82 and 83), including a - # mixed-age couple's joint award (reg 3(2)(a)). The one age-based - # exception, SI 2014/1230 reg 60C (NI: SR 2016/226 reg 61C), covers - # only claims where every claimant has reached the qualifying age for - # State Pension Credit. The cap also applies to Housing Benefit under - # the working-age regulations (HB Regs 2006 Part 8A). Housing Benefit - # under the pension-age regulations, which never apply to a family on - # Universal Credit, has no cap. - return benunit("housing_benefit_pension_age_regulations_apply", period) diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_single_claimant_rate.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_single_claimant_rate.py deleted file mode 100644 index 1e9fe02c0..000000000 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_single_claimant_rate.py +++ /dev/null @@ -1,62 +0,0 @@ -from policyengine_uk.model_api import * -from policyengine_uk.utils.benefit_cap import benefit_cap_couple -from policyengine_uk.utils.uc_work_related_requirements import ( - single_claim_in_rules_shared_with_legacy_benefits, -) - - -class is_benefit_cap_single_claimant_rate(Variable): - value_type = bool - entity = BenUnit - label = "Benefit cap single claimant rate applies" - documentation = ( - "Whether the claimant is a single claimant, not responsible for a " - "child or young person. The model uses one benefit cap for Universal " - "Credit and Housing Benefit, so the family rate applies if either " - "scheme's child or young-person test is met. Couple status " - "(is_couple) stands for joint-claim status, except that where the " - "claimants of a Universal Credit award are entered (is_uc_claimant) " - "and are not Housing Benefit's claimant and partner, the award's " - "claimants decide (benefit_cap_couple), and that a member of a " - "couple who claims " - "Universal Credit as a single person (regulation 3(3)) is a single " - "claimant: regulation 80A sets the limits by single claimant and " - "joint claimants (ADM E5007 note 3). The welfare benefits capped are " - "still the couple's (regulations 78(2) and 79(1)). Housing Benefit " - "has no such single claim, so a family that stays on legacy " - "benefits (reports one and would not claim Universal Credit) keeps " - "the couple rate." - ) - definition_period = YEAR - reference = ( - "https://www.legislation.gov.uk/uksi/2013/376/regulation/80A", - "https://www.legislation.gov.uk/uksi/2013/376/regulation/3", - "https://www.legislation.gov.uk/uksi/2013/376/regulation/78", - "https://www.legislation.gov.uk/uksi/2006/213/regulation/75CA", - "https://www.legislation.gov.uk/uksi/2006/213/regulation/2", - "https://www.legislation.gov.uk/uksi/2006/213/regulation/19", - ) - - def formula(benunit, period, parameters): - person = benunit.members - # Regulation 80A reads the award's claimants: a single claimant or - # joint claimants. Unless is_uc_claimant is entered, the claimants are - # Housing Benefit's claimant and partner, and is_couple (relation_type, - # which can be entered) gives the couple. Where entered claimants of a - # family on Universal Credit are other members, they decide. - couple_of_scheme = benefit_cap_couple(benunit, period) - entered_claimants_differ = benunit.any( - couple_of_scheme != person("is_claimant_or_partner", period) - ) - couple = where( - entered_claimants_differ, - benunit.sum(couple_of_scheme) >= 2, - benunit("is_couple", period), - ) - single_claimant = ~couple | single_claim_in_rules_shared_with_legacy_benefits( - benunit, period - ) - return single_claimant & ~benunit( - "is_responsible_for_child_or_young_person_for_uc_or_housing_benefit", - period, - ) diff --git a/policyengine_uk/variables/gov/dwp/is_lha_shared_accommodation_rate_specified_renter.py b/policyengine_uk/variables/gov/dwp/is_lha_shared_accommodation_rate_specified_renter.py index 92578088c..e858bd882 100644 --- a/policyengine_uk/variables/gov/dwp/is_lha_shared_accommodation_rate_specified_renter.py +++ b/policyengine_uk/variables/gov/dwp/is_lha_shared_accommodation_rate_specified_renter.py @@ -1,7 +1,6 @@ from policyengine_uk.model_api import * from policyengine_uk.utils.uc_work_related_requirements import ( - other_member_of_single_claim_in_shared_rules, - single_claim_in_rules_shared_with_legacy_benefits, + other_member_of_single_claim, ) @@ -10,31 +9,27 @@ class is_lha_shared_accommodation_rate_specified_renter(Variable): entity = BenUnit label = "LHA renter restricted to the shared accommodation rate" documentation = ( - "The modelled specified-renter conditions: no partner, or a member of " - "a couple who claims Universal Credit as a single person (UC " - "regulation 3(3); Schedule 4 paragraph 28(2)), below the shared " - "accommodation age threshold, not responsible for a child or young " - "person under the UC or Housing Benefit rules, no non-dependant under " - "the household composition proxy, and not excepted by a disability " - "benefit (UC Schedule 4 paragraph 29(5)). The age and the exception " - "are the renter's own: the other member of a couple claiming as a " - "single person is neither a renter nor a non-dependant (paragraph " - "9(2)(b)). Other paragraph 29 exceptions are not modelled. The same " - "category serves Housing Benefit, whose young-individual definition " - "(HB regulation 2(1)) has neither the disability exception nor a " - "single claim by a member of a couple. The model applies the " - "disability exception to Housing Benefit too, and the single-claim " - "rule unless the family stays on legacy benefits (reports one and " - "would not claim Universal Credit)." + "The Universal Credit specified-renter conditions (UC Regs 2013 Sch 4 " + "paras 27 and 28): a single person, or a member of a couple claiming " + "as a single person (reg. 3(3)), who is under the shared " + "accommodation age threshold and not excepted by a disability benefit " + "(para 29(5); armed forces independence payment counts as attendance " + "allowance, reg. 2); not responsible for a child or qualifying young " + "person; and with no non-dependant under the household composition " + "proxy. The age and the exception are the renter's own: the other " + "member of a couple claiming as a single person is neither the " + "renter nor a non-dependant (para 9(2)(b)). Other paragraph 29 " + "exceptions are not modelled. Housing Benefit has its own test: see " + "is_housing_benefit_young_individual and housing_benefit_LHA_category." ) definition_period = YEAR reference = ( "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/27", "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/28", "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/29", - "https://www.legislation.gov.uk/uksi/2006/213/regulation/2", - "https://www.legislation.gov.uk/uksi/2006/213/regulation/13D", - "https://www.legislation.gov.uk/uksi/2006/213/regulation/19", + "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/9", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/2", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/3", ) def formula(benunit, period, parameters): @@ -45,10 +40,10 @@ def formula(benunit, period, parameters): # and (b) is not an excepted person". Where a member of a couple # claims as a single person (reg. 3(3)), the renter is that member: # the other member's age and benefits do not count. - claims_as_single_person = single_claim_in_rules_shared_with_legacy_benefits( - benunit, period + claims_as_single_person = benunit( + "uc_member_of_couple_claims_as_single_person", period ) - other_member = other_member_of_single_claim_in_shared_rules(person, period) + other_member = other_member_of_single_claim(person, period) renter = person("is_claimant_or_partner", period) & ~other_member renter_age = benunit.max(where(renter, person("age", period), -np.inf)) # UC Sch 4 para 29(5): a renter under 35 receiving @@ -63,6 +58,10 @@ def formula(benunit, period, parameters): return ( (~benunit("is_couple", period) | claims_as_single_person) & (renter_age < p.shared_accommodation_age_threshold) + # Para 28(3)-(4). A Housing Benefit young person who is not a + # Universal Credit qualifying young person is a non-dependant + # (para 9), which the household composition proxy does not + # identify; either way the renter is not a specified renter. & ~benunit( "is_responsible_for_child_or_young_person_for_uc_or_housing_benefit", period, diff --git a/policyengine_uk/variables/gov/dwp/is_responsible_for_child_or_young_person_for_uc_or_housing_benefit.py b/policyengine_uk/variables/gov/dwp/is_responsible_for_child_or_young_person_for_uc_or_housing_benefit.py index 2897b52f3..532c00fac 100644 --- a/policyengine_uk/variables/gov/dwp/is_responsible_for_child_or_young_person_for_uc_or_housing_benefit.py +++ b/policyengine_uk/variables/gov/dwp/is_responsible_for_child_or_young_person_for_uc_or_housing_benefit.py @@ -4,12 +4,21 @@ class is_responsible_for_child_or_young_person_for_uc_or_housing_benefit(Variable): """Responsible for a child or young person under UC or Housing Benefit rules. - The benefit cap and the LHA category each serve both Universal Credit and - Housing Benefit in this model. UC counts a child or qualifying young person - (UC Regs 2013 regs 4-5); Housing Benefit counts a child or young person, - whose young-person test follows Child Benefit (HB Regs 2006 regs 2, 19). - The family rates apply if either scheme's test is met, so neither scheme's - families are moved onto the single rate by the other's narrower definition. + Universal Credit's LHA shared accommodation test + (is_lha_shared_accommodation_rate_specified_renter) reads this. UC counts + a child or qualifying young person (UC Regs 2013 regs 4-5); Housing + Benefit counts a child or young person, whose young-person test follows + Child Benefit (HB Regs 2006 regs 2, 19). A Housing Benefit young person + who is not a UC qualifying young person is, under UC, a non-dependant + (UC Sch 4 para 9), which the household composition proxy + (lha_renter_has_non_dependant) does not identify. Either way the renter + is not limited to the shared accommodation rate (UC Sch 4 para + 28(3)-(4)), so the UC test reads responsibility under either scheme. + Housing Benefit's own test does not read this: housing_benefit_LHA_category + reads is_housing_benefit_young_individual (is_single_person and age) and + housing_benefit_has_non_dependant (HB reg 13D(2)(a)(i)). The benefit + cap rates, where a non-dependant does not matter, read each scheme's own + responsibility test. UC reg 5(1)(a) also makes every 16-year-old a qualifying young person until the 1 September after their 16th birthday, whatever their education. Annual diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt.py b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt.py new file mode 100644 index 000000000..787ce359a --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt.py @@ -0,0 +1,29 @@ +from policyengine_uk.model_api import * + + +class is_uc_benefit_cap_exempt(Variable): + value_type = bool + entity = BenUnit + label = "Exempt from the Universal Credit benefit cap" + documentation = ( + "Whether regulation 82 (earnings) or 83 (entitlement to or receipt of " + "certain benefits) of the UC Regs 2013 lifts the benefit cap " + "(reg. 79(1)), or SI 2014/1230 reg. 60C disapplies it because every " + "claimant has reached the qualifying age for State Pension Credit. " + "That is the cap's only age exception: a mixed-age couple's joint " + "award is capped like any other." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2013/376/regulation/79", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/82", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/83", + "https://www.legislation.gov.uk/uksi/2014/1230/regulation/60C", + ) + + def formula(benunit, period, parameters): + return ( + benunit("is_uc_benefit_cap_exempt_earnings", period) + | benunit("is_uc_benefit_cap_exempt_specified_benefit", period) + | benunit("is_uc_benefit_cap_exempt_qualifying_age", period) + ) diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt_earnings.py similarity index 56% rename from policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py rename to policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt_earnings.py index c859af32b..ec478db21 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt_earnings.py @@ -1,16 +1,22 @@ from policyengine_uk.model_api import * -class is_benefit_cap_exempt_earnings(Variable): +class is_uc_benefit_cap_exempt_earnings(Variable): value_type = bool entity = BenUnit - label = "Exempt from the benefit cap because of earnings" + label = "Exempt from the Universal Credit benefit cap because of earnings" documentation = ( - "The assessed claimant's or couple's actual net earned income for " - "Universal Credit reaches the earnings threshold. This includes " - "statutory pay and deducts their own relievable pension contributions " - "and tax and National Insurance on earnings. Income imputed by the " - "minimum income floor and a dependant's earnings do not count." + "Whether the claimant's earned income, or a couple's combined earned " + "income, reaches the earnings threshold (UC Regs 2013 reg. 82(1)(a)). " + "The measure is the assessed claimants' actual net earned income for " + "Universal Credit: it includes statutory pay and deducts their own " + "relievable pension contributions and tax and National Insurance on " + "earnings. Income imputed by the minimum income floor (reg. 82(4)) " + "and a dependant's earnings do not count. For a member of a couple " + "claiming as a single person the couple's earnings combine. The " + "grace period (reg. 82(1)(b)) is not modelled. Housing Benefit has " + "no earnings exception: its work exception is entitlement to working " + "tax credit (HB Regs 2006 reg. 75E)." ) definition_period = YEAR reference = ( diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt_qualifying_age.py b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt_qualifying_age.py new file mode 100644 index 000000000..71f23b991 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt_qualifying_age.py @@ -0,0 +1,42 @@ +from policyengine_uk.model_api import * + + +class is_uc_benefit_cap_exempt_qualifying_age(Variable): + value_type = bool + entity = BenUnit + label = "Exempt from the Universal Credit benefit cap at State Pension Credit age" + documentation = ( + "Whether the single claimant, or both joint claimants, have reached " + "the qualifying age for State Pension Credit. SI 2014/1230 reg. 60C " + "disapplies the cap (UC Regs 2013 reg. 79) for a qualifying claim " + "made by such claimants. A claimant over that age does not meet the " + "basic condition in WRA 2012 s. 4(1)(b); a couple may claim jointly " + "where only one member is over it (UC Regs 2013 reg. 3(2)(a)), and " + "reg. 60A waives the condition only for a qualifying claim by " + "claimants migrated from working tax credit. So an award to such " + "claimants is one to which reg. 60C applies. The model reaches it in " + "two ways: a reg. 60A claim by a Working Tax Credit recipient whose " + "tax credits closed (claims_uc_at_legacy_closure; is_uc_eligible), and " + "an award supplied as an input. A mixed-age " + "couple's joint award is capped like any other. A member of a couple " + "who claims as a single person (UC Regs 2013 reg. 3(3)) is a single " + "claimant, so only their own age counts." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2014/1230/regulation/60C", + "https://www.legislation.gov.uk/uksi/2014/1230/regulation/60A", + "https://www.legislation.gov.uk/ukpga/2012/5/section/4", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/3", + ) + + def formula(benunit, period, parameters): + person = benunit.members + # The claimants: the single claimant or each joint claimant (WRA 2012 + # s. 40). A partner who cannot be a joint claimant (reg. 3(3)) is not + # one. That holds whatever the claimant's age: the reg. 3(3) routing + # in uc_member_of_couple_claims_as_single_person needs a claimant under + # the qualifying age, which a reg. 60C claimant never is. + claimant = person("is_uc_single_or_joint_claimant", period) + attained = person("has_attained_state_pension_credit_qualifying_age", period) + return benunit.any(claimant) & benunit.all(attained | ~claimant) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt_specified_benefit.py b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt_specified_benefit.py new file mode 100644 index 000000000..72d1b3d39 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_exempt_specified_benefit.py @@ -0,0 +1,209 @@ +import os + +from policyengine_uk.model_api import * +from policyengine_uk.utils.benefit_unit import award_of_members +from policyengine_uk.utils.supplied_inputs import supplied_input +from policyengine_uk.utils.uc_work_related_requirements import ( + other_member_of_single_claim, +) +from policyengine_uk.variables.gov.dwp._legacy_award_payee import ( + is_payee_of_couple_award, +) +from policyengine_uk.variables.gov.dwp.esa_income import income_related_esa_award +from policyengine_uk.variables.gov.dwp.universal_credit.carer_element.uc_carer_element import ( + uc_carer_element, +) +from policyengine_uk.variables.gov.dwp.universal_credit.disability_element.limited_work_ability.uc_LCWRA_element import ( + uc_LCWRA_element, +) + +# UC Regs 2013 reg 83(1) names, for each benefit, whose receipt (or +# entitlement) lifts the cap. + +# "a claimant is receiving" (reg 83(1)(b)-(e)): industrial injuries benefit, +# attendance allowance, which reg 2 defines to include armed forces +# independence payment, and a war pension, which includes the Armed Forces +# Compensation Scheme's guaranteed income payments. +CLAIMANT_BENEFITS = [ + "attendance_allowance", + "armed_forces_independence_payment", + "iidb", + "afcs", +] + +# "a claimant, or a child or qualifying young person for whom a claimant is +# responsible" (reg 83(1)(f)). +CHILD_OR_QUALIFYING_YOUNG_PERSON_BENEFITS = [ + "dla", +] + +# "a claimant, or a qualifying young person for whom a claimant is +# responsible" (reg 83(1)(g), (i), (ia)). Carer's allowance and carer support +# payment count by entitlement, not payment: (i)-(ia) name a person "entitled +# to" them, even if an overlapping benefit reduces the payment to nil. +QUALIFYING_YOUNG_PERSON_BENEFITS = [ + "pip", + "is_entitled_to_carer_benefit", +] + + +def calculated_by(population, variable_name, period, model_variable): + """Whether the simulation calculates ``variable_name`` with the model's own + formula (``model_variable``'s), rather than one a reform put in its place. + The tax-benefit system loads each variable file as its own module, so the + formula is matched by where its code is, not by identity.""" + variable = population.simulation.tax_benefit_system.get_variable(variable_name) + formula = variable.get_formula(period) + code = getattr(formula, "__code__", None) + model = model_variable.formula.__code__ + return code is not None and ( + os.path.realpath(code.co_filename), + code.co_firstlineno, + ) == (os.path.realpath(model.co_filename), model.co_firstlineno) + + +class is_uc_benefit_cap_exempt_specified_benefit(Variable): + value_type = bool + entity = BenUnit + label = "Exempt from the Universal Credit benefit cap through a specified benefit or element" + documentation = ( + "Whether the award includes the LCWRA or carer element, or a " + "claimant (or, for some benefits, a child or qualifying young person " + "a claimant is responsible for) receives or is entitled to a benefit " + "listed in UC Regs 2013 reg. 83(1): employment and support allowance " + "with the support component, industrial injuries benefit, attendance " + "allowance (including armed forces independence payment, reg. 2), a " + "war pension or armed forces compensation, disability living " + "allowance, personal independence payment, carer's allowance or " + "carer support payment. A claimant is the single claimant or each " + "joint claimant (is_uc_assessed_claimant): a partner who cannot be a " + "joint claimant, so that the other member claims as a single person " + "(reg. 3(3)), is not one, and another member of the benefit unit, " + "such as a non-dependent adult, does not lift the cap with their own " + "benefit. Working tax credit, which cannot be paid with Universal " + "Credit, is Housing Benefit's exception (HB Regs 2006 reg. 75E), not " + "this one." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2013/376/regulation/83", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/2", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/3", + "https://www.gov.uk/benefit-cap/when-youre-not-affected", + ) + + def formula(benunit, period, parameters): + person = benunit.members + # The members of the couple whose income the award assesses + # (is_uc_assessed_claimant), including the other member of a couple + # where one member claims as a single person (reg 78(2)). Reg 83(1) + # reads "a claimant": that other member (reg 3(3)) is a member of the + # couple but not a claimant, so their own benefits and limited + # capability do not lift the cap. + couple = person("is_uc_assessed_claimant", period) + claimant = couple & ~other_member_of_single_claim(person, period) + + # A child or qualifying young person a claimant is responsible for + # (UC Regs 2013 regs 4-5), which includes every 16-year-old until the + # 1 September after their birthday (reg 5(1)(a)), unless they receive + # Universal Credit, ESA or JSA themselves (reg 5(5)). Annual ages + # cannot place that date, so any such 16-year-old counts, as for the + # family rate in is_uc_benefit_cap_single_claimant_rate. + age = person("age", period) + child_or_young_person = ~couple & person( + "is_child_or_qualifying_young_person_for_universal_credit", period + ) + sixteen = ( + (age >= 16) + & (age < 17) + & ~couple + & ~person("is_looked_after_by_local_authority", period) + & ~person("receives_benefits_in_own_right", period) + ) + young_person = ( + child_or_young_person + & person("is_qualifying_young_person_for_universal_credit", period) + ) | sixteen + child_or_young_person = child_or_young_person | sixteen + + def received_by(benefits, members): + return add_for_members(benunit, period, benefits, members) > 0 + + # Reg 83(1)(a): "the claimant is receiving an employment and support + # allowance ... which includes a support component". Contributory and + # income-related allowances both carry it; another member's allowance + # does not count. The couple's income-related award is the part of + # the family's award on the couple's reports, as in + # claimant_or_partner_esa_income, and it is received by its payee + # (is_payee_of_couple_award). + couple_esa_award = award_of_members( + benunit, + period, + "esa_income", + "esa_income_reported", + income_related_esa_award, + couple, + ) + receiving_income_related_esa = benunit.project( + couple_esa_award > 0 + ) & is_payee_of_couple_award( + person, period, "esa_income_reported", couple=couple + ) + receiving_esa = (person("esa_contrib", period) > 0) | ( + receiving_income_related_esa + ) + esa_support_component = benunit.any( + claimant & receiving_esa & person("esa_includes_support_component", period) + ) + + # Reg 83(1)(a), (j): the LCWRA or carer element is included in the + # award. An element entered for the award is included exactly when it + # is positive, whatever the members' circumstances (an entry of nil + # means the award does not include it). An element a reform's formula + # gives is included when positive. The model's own formula still + # gives the element for any member's limited capability for work and + # work-related activity or caring responsibilities, but the element is + # for "a claimant" (regs 27(1), 29(1)). So the element it gives is + # included when positive unless the only members with the + # circumstance are not claimants. A circumstance entered for the + # family alone (benunit_has_carer) names no member, and a nil amount + # (benunit_has_carer entered as false, or a nil rate) is not included. + # The Universal Credit rebalancing rescales the formula's elements + # from 2026 (scenarios/uc_reform.py) and is read the same way. The + # model does not have the reg 28 waiting period or regs 29(5)-(6), + # 30(3). Where reg 29(4) leaves a carer with limited capability only + # the LCWRA element, that element exempts. + def element_included(element, model_variable, circumstance): + entered = supplied_input(benunit, element, period) + if entered is not None: + return entered > 0 + positive = benunit(element, period) > 0 + if not calculated_by(benunit, element, period, model_variable): + return positive + only_other_members = benunit.any(~claimant & circumstance) & ~benunit.any( + claimant & circumstance + ) + return positive & ~only_other_members + + lcwra_element = element_included( + "uc_LCWRA_element", + uc_LCWRA_element, + person("uc_limited_capability_for_WRA", period), + ) + carer_element = element_included( + "uc_carer_element", + uc_carer_element, + person("is_carer_for_benefits", period), + ) + + return ( + esa_support_component + | received_by(CLAIMANT_BENEFITS, claimant) + | received_by( + CHILD_OR_QUALIFYING_YOUNG_PERSON_BENEFITS, + claimant | child_or_young_person, + ) + | received_by(QUALIFYING_YOUNG_PERSON_BENEFITS, claimant | young_person) + | lcwra_element + | carer_element + ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_single_claimant_rate.py b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_single_claimant_rate.py new file mode 100644 index 000000000..cbe473a2c --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/is_uc_benefit_cap_single_claimant_rate.py @@ -0,0 +1,56 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.benefit_unit import has_sixteen_year_old_dependant + + +class is_uc_benefit_cap_single_claimant_rate(Variable): + value_type = bool + entity = BenUnit + label = "Universal Credit benefit cap single claimant rate applies" + documentation = ( + "Whether the Universal Credit claimant is a single claimant who is " + "not responsible for a child or qualifying young person (UC Regs 2013 " + "reg. 80A(2)(a) and (c)). Joint claimants, and a single claimant " + "responsible for a child or qualifying young person, have the other " + "rate. Couple status (is_couple) stands for joint-claim status, " + "except that where the claimants of the award are entered " + "(is_uc_claimant) and are not the benefit unit's claimant and " + "partner, the award's claimants decide. A member of a couple who " + "claims as a single person because the other member cannot be a " + "joint claimant (reg. 3(3)) is a single claimant (ADM E5007 note 3), " + "although the welfare benefits capped are still the couple's (regs. " + "78(2) and 79(1)). A 16-year-old member counts as a qualifying young " + "person for the year (reg. 5(1)(a))." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2013/376/regulation/80A", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/3", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/4", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/5", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/78", + ) + + def formula(benunit, period, parameters): + person = benunit.members + # Regulation 80A reads the award's claimants: a single claimant or + # joint claimants. Unless is_uc_claimant is entered, the claimants are + # the claimant and partner, and is_couple (relation_type, which can be + # entered) gives the couple. Where entered claimants are other + # members, they decide. + claimants = person("is_uc_assessed_claimant", period) + entered_claimants_differ = benunit.any( + claimants != person("is_claimant_or_partner", period) + ) + couple = where( + entered_claimants_differ, + benunit.sum(claimants) >= 2, + benunit("is_couple", period), + ) + single_claimant = ~couple | benunit( + "uc_member_of_couple_claims_as_single_person", period + ) + responsible = benunit( + "is_responsible_for_child_or_qualifying_young_person_for_universal_credit", + period, + ) | has_sixteen_year_old_dependant(benunit, period) + return single_claimant & ~responsible diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/uc_benefit_cap.py b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/uc_benefit_cap.py new file mode 100644 index 000000000..724404503 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/uc_benefit_cap.py @@ -0,0 +1,29 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.benefit_cap import benefit_cap_annual_limit + + +class uc_benefit_cap(Variable): + value_type = float + entity = BenUnit + label = "Universal Credit benefit cap" + documentation = ( + "The applicable annual limit on the welfare benefits of a Universal " + "Credit claimant or couple (UC Regs 2013 reg. 80A(2)), or infinity " + "where regulation 82 or 83 lifts the cap (reg. 79(1))." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/2013/376/regulation/79", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/80A", + ) + + def formula(benunit, period, parameters): + limit = benefit_cap_annual_limit( + benunit, + period, + parameters, + benunit("is_uc_benefit_cap_single_claimant_rate", period), + ) + exempt = benunit("is_uc_benefit_cap_exempt", period) + return where(exempt, np.inf, limit) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/uc_benefit_cap_reduction.py b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/uc_benefit_cap_reduction.py new file mode 100644 index 000000000..eacb26668 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/uc_benefit_cap_reduction.py @@ -0,0 +1,40 @@ +from policyengine_uk.model_api import * + + +class uc_benefit_cap_reduction(Variable): + value_type = float + entity = BenUnit + label = "Universal Credit benefit cap reduction" + documentation = ( + "The reduction the benefit cap makes to a Universal Credit award: the " + "excess of the welfare benefits of the single person or couple over " + "the Universal Credit cap, minus the childcare costs element, and " + "nothing where the childcare costs element is greater than the " + "excess (UC Regs 2013 reg. 81). Housing Benefit counts as nil in the " + "welfare benefits (reg. 80(2A)). The cap reduces an award of Universal " + "Credit (reg. 78(1)), so there is no reduction without one. This is the " + "amount reg. 81 takes off, which can exceed the award before the cap; " + "universal_credit floors the award at nil, so the amount actually " + "removed is the smaller of the two." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/2013/376/regulation/78", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/79", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/80", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/81", + ) + + def formula(benunit, period, parameters): + # Reg 80(2A): where the welfare benefit is Housing Benefit, "the + # amount to be used is nil". benefit_cap_welfare_benefits counts it + # for the Housing Benefit cap (HB Regs 2006 reg 75C(2)(b)), so it is + # taken out here; a family paid both is capped on its other benefits. + welfare_benefits = benunit("benefit_cap_welfare_benefits", period) - benunit( + "housing_benefit_pre_benefit_cap", period + ) + excess = max_(welfare_benefits - benunit("uc_benefit_cap", period), 0) + reduction = max_(excess - benunit("uc_childcare_element", period), 0) + has_award = benunit("universal_credit_pre_benefit_cap", period) > 0 + return where(has_award, reduction, 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 0ef750fec..0daf7d74a 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 @@ -26,7 +26,7 @@ def formula(benunit, period, parameters): standard_allowance = benunit("uc_standard_allowance", period) award = max_( benunit("universal_credit_pre_benefit_cap", period) - - benunit("benefit_cap_reduction", period), + - benunit("uc_benefit_cap_reduction", period), 0, ) minimum_payable = p.minimum_payable * MONTHS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_has_deduction.py b/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_has_deduction.py index 9ae0e218f..c909f8a67 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_has_deduction.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/deductions/uc_has_deduction.py @@ -36,7 +36,7 @@ def formula(benunit, period, parameters): adjusted_incidence = clip(incidence * p.region_incidence_factor[region], 0, 1) award = max_( benunit("universal_credit_pre_benefit_cap", period) - - benunit("benefit_cap_reduction", period), + - benunit("uc_benefit_cap_reduction", period), 0, ) on_uc = benunit("claims_universal_credit", period) & (award > 0) 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 a77e418ca..3f512f70e 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/universal_credit.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/universal_credit.py @@ -11,7 +11,7 @@ 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) + 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