diff --git a/changelog.d/2004.fixed.md b/changelog.d/2004.fixed.md new file mode 100644 index 0000000000..2087926885 --- /dev/null +++ b/changelog.d/2004.fixed.md @@ -0,0 +1 @@ +The model now calculates Housing Benefit and national Council Tax Reduction non-dependant deductions under each scheme's rules. Bands include their lower edge. For a non-exempt non-dependant outside remunerative work, including someone on Income Support, income-based JSA or income-related ESA, the council deducts the lowest amount whatever their income. The council bands a couple on its joint gross income plus the family's awards, counting Universal Credit after the benefit cap and before debt deductions, and normally makes one deduction, the higher. Under Wales's working-age scheme, it deducts both members' amounts when their own Universal Credit award assesses either member. It deducts separately for each other adult. The model counts other investment income, ISA interest and ISA dividends in gross income, and treats a self-employment or property loss as nil. Councils apply the claimant and non-dependant exemptions, and for the Universal Credit exemption the model reads Universal Credit's own calculation of earned income. The model gives the England pensioner, Scottish and Welsh Council Tax Reduction schemes their own scales from 2013-14, and follows each jointly liable applicant's own scheme. For the Universal Credit benefit cap, the model now totals welfare benefits without Housing Benefit (`uc_benefit_cap_welfare_benefits`). A caller who supplies the combined total, `benefit_cap_welfare_benefits`, must also supply `housing_benefit_pre_benefit_cap` or `uc_benefit_cap_welfare_benefits` for every benefit unit; otherwise the model raises `ValueError` when it calculates the Universal Credit cap. diff --git a/changelog.d/2008.fixed.md b/changelog.d/2008.fixed.md new file mode 100644 index 0000000000..11354b34f2 --- /dev/null +++ b/changelog.d/2008.fixed.md @@ -0,0 +1 @@ +Give each member of the benefit unit aged 16 or over who is not the claimant or partner a bedroom in the Universal Credit LHA size criteria (UC Regs 2013 Sch 4 para 10(1)(b) and (c)), except a qualifying young person no one is responsible for (para 9(2)(g)) and a household head who does not claim and is not a qualifying young person the renter is responsible for, whom the formula treats as liable for the rent (para 9(2)(d)). The bedroom reads the definition that the housing cost contribution uses (is_benefit_unit_non_dependant_for_universal_credit), and the claim that bears an in-unit non-dependant's contribution has their bedroom. diff --git a/changelog.d/2014.fixed.md b/changelog.d/2014.fixed.md new file mode 100644 index 0000000000..7f69203350 --- /dev/null +++ b/changelog.d/2014.fixed.md @@ -0,0 +1 @@ +- Charge Universal Credit, Housing Benefit and national Council Tax Reduction non-dependant deductions for members of the claimant's own benefit unit who are not the claimant, partner or a child or young person, such as an adult son (UC Regs 2013 Sch 4 para 9; HB Regs 2006 and HB (SPC) Regs 2006 reg 3; SI 2012/2885 reg 9 and the Welsh and Scottish equivalents). Housing Benefit and Council Tax Reduction apportion every non-dependant between joint occupiers or jointly liable applicants, a lodger's adult son included, and a household head is never her own benefit unit's non-dependant. Apply the Universal Credit renter exemption from housing cost contributions (Sch 4 para 15), and give the para 16(2)(b) Pension Credit exemption only to the claimant or partner who receives it, so a pensioner's adult son is no longer exempt by her award. diff --git a/docs/book/programs/gov/dwp/housing-benefit.ipynb b/docs/book/programs/gov/dwp/housing-benefit.ipynb index 90c8415910..64f220a8ba 100644 --- a/docs/book/programs/gov/dwp/housing-benefit.ipynb +++ b/docs/book/programs/gov/dwp/housing-benefit.ipynb @@ -42,7 +42,7 @@ "\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", + "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)). Under reg 74 (SI 2006/214 reg 55), for a non-dependant aged 18 or over in remunerative work (16 hours a week or more) the council deducts the amount for the band containing their normal gross weekly income, counting a couple's joint income and including each band's lower edge; for one not in remunerative work it deducts the lowest amount. It makes one deduction for a couple, the higher. Councils make no deduction for full-time students, State Pension Credit recipients, or under-25s on Income Support, income-based JSA or Universal Credit without earned income, and none for any non-dependant if the claimant or partner is blind or receives Attendance Allowance, the DLA care component, the PIP daily living component or Armed Forces Independence Payment. 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 `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", diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/age_threshold.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/age_threshold.yaml index c08cba87a4..d22b71993c 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/age_threshold.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/age_threshold.yaml @@ -1,11 +1,13 @@ -description: A non dependent deduction is provided under Housing Benefit for filers at or above this age threshold. +description: Housing Benefit makes a non-dependant deduction only for non-dependants at or above this age. metadata: label: Housing benefit non dependent deduction age threshold period: year unit: year - reference: - - title: The Housing Benefit Regulations 2006 sec. 74 (1) - href: https://www.legislation.gov.uk/uksi/2006/213/regulation/74/2024-04-01 + reference: + - title: The Housing Benefit Regulations 2006 reg. 74(1) + href: https://www.legislation.gov.uk/uksi/2006/213/regulation/74 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 reg. 55(1) + href: https://www.legislation.gov.uk/uksi/2006/214/regulation/55 values: - 2019-04-01: 18 + 2006-03-06: 18 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/income_related_benefit_age_limit.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/income_related_benefit_age_limit.yaml new file mode 100644 index 0000000000..643e0c4133 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/non_dep_deduction/income_related_benefit_age_limit.yaml @@ -0,0 +1,12 @@ +description: Housing Benefit makes no deduction for a non-dependant under this age who is on Income Support or income-based Jobseeker's Allowance, or entitled to Universal Credit calculated on the basis that they have no earned income. +values: + 2006-03-06: 25 +metadata: + label: Housing Benefit non-dependant income-related benefit exemption age limit + unit: year + period: year + reference: + - title: The Housing Benefit Regulations 2006 reg. 74(8) + href: https://www.legislation.gov.uk/uksi/2006/213/regulation/74 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 reg. 55(8) + href: https://www.legislation.gov.uk/uksi/2006/214/regulation/55 diff --git a/policyengine_uk/parameters/gov/local_authorities/england/council_tax_reduction/pensioners/non_dep_deduction/amount.yaml b/policyengine_uk/parameters/gov/local_authorities/england/council_tax_reduction/pensioners/non_dep_deduction/amount.yaml new file mode 100644 index 0000000000..f7d2106bc1 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/england/council_tax_reduction/pensioners/non_dep_deduction/amount.yaml @@ -0,0 +1,154 @@ +description: England pensioner Council Tax Reduction deducts this weekly amount for each non-dependant aged 18 or over, by the non-dependant's normal gross weekly income if they are in remunerative work; a non-dependant not in remunerative work pays the lowest amount. + +# Sums from paragraph 8 of Schedule 1 to the Council Tax Reduction Schemes +# (Prescribed Requirements) (England) Regulations 2012 (SI 2012/2885), as +# substituted for each financial year by the amending instruments in the +# references. Each year's sums apply from 1 April. +brackets: +- threshold: + 2013-04-01: 0 + amount: + 2013-04-01: 3.65 + 2014-04-01: 3.7 + 2015-04-01: 3.74 + 2016-04-01: 3.77 + 2017-04-01: 3.8 + 2018-04-01: 3.9 + 2019-04-01: 4 + 2020-04-01: 4.05 + 2021-04-01: 4.05 + 2022-04-01: 4.2 + 2023-04-01: 4.6 + 2024-04-01: 4.9 + 2025-04-01: 5 + 2026-04-01: 5.2 +- threshold: + 2013-04-01: 186 + 2014-04-01: 188 + 2015-04-01: 189 + 2016-04-01: 195 + 2017-04-01: 196.95 + 2018-04-01: 202.85 + 2019-04-01: 207.7 + 2020-04-01: 217 + 2021-04-01: 217 + 2022-04-01: 224 + 2023-04-01: 236 + 2024-04-01: 256 + 2025-04-01: 266 + 2026-04-01: 279 + amount: + 2013-04-01: 7.25 + 2014-04-01: 7.45 + 2015-04-01: 7.52 + 2016-04-01: 7.58 + 2017-04-01: 7.65 + 2018-04-01: 7.9 + 2019-04-01: 8.1 + 2020-04-01: 8.25 + 2021-04-01: 8.3 + 2022-04-01: 8.55 + 2023-04-01: 9.4 + 2024-04-01: 10.05 + 2025-04-01: 10.2 + 2026-04-01: 10.6 +- threshold: + 2013-04-01: 322 + 2014-04-01: 326 + 2015-04-01: 328 + 2016-04-01: 338 + 2017-04-01: 341.4 + 2018-04-01: 351.65 + 2019-04-01: 360.1 + 2020-04-01: 377 + 2021-04-01: 377 + 2022-04-01: 389 + 2023-04-01: 410 + 2024-04-01: 445 + 2025-04-01: 463 + 2026-04-01: 485 + amount: + 2013-04-01: 9.15 + 2014-04-01: 9.4 + 2015-04-01: 9.49 + 2016-04-01: 9.56 + 2017-04-01: 9.65 + 2018-04-01: 9.95 + 2019-04-01: 10.2 + 2020-04-01: 10.35 + 2021-04-01: 10.4 + 2022-04-01: 10.7 + 2023-04-01: 11.8 + 2024-04-01: 12.6 + 2025-04-01: 12.8 + 2026-04-01: 13.3 +- threshold: + 2013-04-01: 401 + 2014-04-01: 406 + 2015-04-01: 408 + 2016-04-01: 420 + 2017-04-01: 424.2 + 2018-04-01: 436.9 + 2019-04-01: 447.4 + 2020-04-01: 469 + 2021-04-01: 469 + 2022-04-01: 484 + 2023-04-01: 511 + 2024-04-01: 554 + 2025-04-01: 577 + 2026-04-01: 605 + amount: + 2013-04-01: 10.95 + 2014-04-01: 11.25 + 2015-04-01: 11.36 + 2016-04-01: 11.45 + 2017-04-01: 11.55 + 2018-04-01: 11.9 + 2019-04-01: 12.2 + 2020-04-01: 12.4 + 2021-04-01: 12.45 + 2022-04-01: 12.85 + 2023-04-01: 14.15 + 2024-04-01: 15.1 + 2025-04-01: 15.35 + 2026-04-01: 15.95 +metadata: + label: England pensioner CTR non-dependant deduction + amount_unit: currency-GBP + threshold_unit: currency-GBP + period: week + type: single_amount + economy: false + propagate_metadata_to_children: true + uprating: gov.benefit_uprating_cpi + reference: + - title: Council Tax Reduction Schemes (Prescribed Requirements) (England) Regulations 2012, Sch. 1 para. 8 + href: https://www.legislation.gov.uk/uksi/2012/2885/schedule/1/paragraph/8 + - title: SI 2012/3085, sums for 2013-14 + href: https://www.legislation.gov.uk/uksi/2012/3085/made + - title: SI 2013/3181, sums for 2014-15 + href: https://www.legislation.gov.uk/uksi/2013/3181/made + - title: SI 2014/3312, sums for 2015-16 + href: https://www.legislation.gov.uk/uksi/2014/3312/made + - title: SI 2015/2041, sums for 2016-17 + href: https://www.legislation.gov.uk/uksi/2015/2041/made + - title: SI 2016/1262, sums for 2017-18 + href: https://www.legislation.gov.uk/uksi/2016/1262/made + - title: SI 2017/1305, sums for 2018-19 + href: https://www.legislation.gov.uk/uksi/2017/1305/made + - title: SI 2018/1346, sums for 2019-20 + href: https://www.legislation.gov.uk/uksi/2018/1346/made + - title: SI 2020/23, sums for 2020-21 + href: https://www.legislation.gov.uk/uksi/2020/23/made + - title: SI 2021/29, sums for 2021-22 + href: https://www.legislation.gov.uk/uksi/2021/29/made + - title: SI 2022/25, sums for 2022-23 + href: https://www.legislation.gov.uk/uksi/2022/25/made + - title: SI 2023/16, sums for 2023-24 + href: https://www.legislation.gov.uk/uksi/2023/16/made + - title: SI 2024/29, sums for 2024-25 + href: https://www.legislation.gov.uk/uksi/2024/29/made + - title: SI 2025/39, sums for 2025-26 + href: https://www.legislation.gov.uk/uksi/2025/39/made + - title: SI 2026/27, sums for 2026-27 + href: https://www.legislation.gov.uk/uksi/2026/27/made diff --git a/policyengine_uk/parameters/gov/local_authorities/england/council_tax_reduction/pensioners/non_dep_deduction/exempt_universal_credit_without_earned_income.yaml b/policyengine_uk/parameters/gov/local_authorities/england/council_tax_reduction/pensioners/non_dep_deduction/exempt_universal_credit_without_earned_income.yaml new file mode 100644 index 0000000000..75410b1ece --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/england/council_tax_reduction/pensioners/non_dep_deduction/exempt_universal_credit_without_earned_income.yaml @@ -0,0 +1,11 @@ +description: England pensioner Council Tax Reduction makes no deduction for a non-dependant entitled to Universal Credit calculated on the basis that they have no earned income, from the year this applies (Sch. 1 para. 8(8)(c)). +values: + 2013-04-01: false + 2015-04-01: true +metadata: + label: England pensioner CTR exempts non-dependants on Universal Credit without earned income + unit: bool + period: year + reference: + - title: SI 2012/2885 Sch. 1 para. 8(8)(c), inserted for 2015-16 by SI 2014/3312 reg. 2(5)(a)(vi) + href: https://www.legislation.gov.uk/uksi/2014/3312/regulation/2/made diff --git a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/non_dep_deduction/amount.yaml b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/non_dep_deduction/amount.yaml new file mode 100644 index 0000000000..3b5c0874b7 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/non_dep_deduction/amount.yaml @@ -0,0 +1,160 @@ +description: Scotland Council Tax Reduction deducts this weekly amount for each non-dependant aged 18 or over, by the non-dependant's normal gross weekly income if they are in remunerative work; a non-dependant not in remunerative work pays the lowest amount. + +# Sums from regulation 48 of the Council Tax Reduction (State Pension Credit) +# (Scotland) Regulations 2012 (SSI 2012/319) and, for people under pension +# age, regulation 67 of SSI 2012/303 to 2021-22 and regulation 90 of the +# Council Tax Reduction (Scotland) Regulations 2021 (SSI 2021/249) from +# 2022-23, as substituted for each financial year by the amending instruments +# in the references. Each year's sums apply from 1 April. +brackets: +- threshold: + 2013-04-01: 0 + amount: + 2013-04-01: 3.65 + 2014-04-01: 3.75 + 2015-04-01: 3.85 + 2016-04-01: 3.9 + 2017-04-01: 3.95 + 2018-04-01: 4.05 + 2019-04-01: 4.15 + 2020-04-01: 4.25 + 2021-04-01: 4.3 + 2022-04-01: 4.45 + 2023-04-01: 4.9 + 2024-04-01: 5.25 + 2025-04-01: 5.35 + 2026-04-01: 5.55 +- threshold: + 2013-04-01: 186 + 2014-04-01: 188 + 2015-04-01: 189 + 2016-04-01: 195 + 2017-04-01: 200 + 2018-04-01: 204 + 2019-04-01: 209 + 2020-04-01: 213 + 2021-04-01: 213 + 2022-04-01: 228 + 2023-04-01: 241 + 2024-04-01: 260 + 2025-04-01: 273 + 2026-04-01: 286 + amount: + 2013-04-01: 7.25 + 2014-04-01: 7.5 + 2015-04-01: 7.7 + 2016-04-01: 7.75 + 2017-04-01: 7.85 + 2018-04-01: 8.1 + 2019-04-01: 8.3 + 2020-04-01: 8.45 + 2021-04-01: 8.5 + 2022-04-01: 8.8 + 2023-04-01: 9.7 + 2024-04-01: 10.35 + 2025-04-01: 10.55 + 2026-04-01: 10.95 +- threshold: + 2013-04-01: 322 + 2014-04-01: 326 + 2015-04-01: 328 + 2016-04-01: 338 + 2017-04-01: 346 + 2018-04-01: 354 + 2019-04-01: 363 + 2020-04-01: 370 + 2021-04-01: 370 + 2022-04-01: 396 + 2023-04-01: 418 + 2024-04-01: 451 + 2025-04-01: 474 + 2026-04-01: 497 + amount: + 2013-04-01: 9.15 + 2014-04-01: 9.5 + 2015-04-01: 9.8 + 2016-04-01: 9.85 + 2017-04-01: 9.95 + 2018-04-01: 10.25 + 2019-04-01: 10.5 + 2020-04-01: 10.7 + 2021-04-01: 10.8 + 2022-04-01: 11.15 + 2023-04-01: 12.3 + 2024-04-01: 13.15 + 2025-04-01: 13.4 + 2026-04-01: 13.9 +- threshold: + 2013-04-01: 401 + 2014-04-01: 406 + 2015-04-01: 408 + 2016-04-01: 420 + 2017-04-01: 430 + 2018-04-01: 439 + 2019-04-01: 451 + 2020-04-01: 458 + 2021-04-01: 458 + 2022-04-01: 490 + 2023-04-01: 517 + 2024-04-01: 558 + 2025-04-01: 586 + 2026-04-01: 614 + amount: + 2013-04-01: 10.95 + 2014-04-01: 11.35 + 2015-04-01: 11.7 + 2016-04-01: 11.8 + 2017-04-01: 11.9 + 2018-04-01: 12.25 + 2019-04-01: 12.55 + 2020-04-01: 12.8 + 2021-04-01: 12.9 + 2022-04-01: 13.3 + 2023-04-01: 14.65 + 2024-04-01: 15.65 + 2025-04-01: 15.95 + 2026-04-01: 16.55 +metadata: + label: Scotland CTR non-dependant deduction + amount_unit: currency-GBP + threshold_unit: currency-GBP + period: week + type: single_amount + economy: false + propagate_metadata_to_children: true + uprating: gov.benefit_uprating_cpi + reference: + - title: Council Tax Reduction (State Pension Credit) (Scotland) Regulations 2012, reg. 48 + href: https://www.legislation.gov.uk/ssi/2012/319/regulation/48 + - title: Council Tax Reduction (Scotland) Regulations 2012, reg. 67 (to 31 March 2022) + href: https://www.legislation.gov.uk/ssi/2012/303/regulation/67 + - title: Council Tax Reduction (Scotland) Regulations 2021, reg. 90 + href: https://www.legislation.gov.uk/ssi/2021/249/regulation/90 + - title: SSI 2013/49, sums for 2013-14 + href: https://www.legislation.gov.uk/ssi/2013/49/made + - title: SSI 2014/35, sums for 2014-15 + href: https://www.legislation.gov.uk/ssi/2014/35/made + - title: SSI 2015/46, sums for 2015-16 + href: https://www.legislation.gov.uk/ssi/2015/46/made + - title: SSI 2016/81, sums for 2016-17 + href: https://www.legislation.gov.uk/ssi/2016/81/made + - title: SSI 2017/41, sums for 2017-18 + href: https://www.legislation.gov.uk/ssi/2017/41/made + - title: SSI 2018/69, sums for 2018-19 + href: https://www.legislation.gov.uk/ssi/2018/69/made + - title: SSI 2019/29, sums for 2019-20 + href: https://www.legislation.gov.uk/ssi/2019/29/made + - title: SSI 2020/25, sums for 2020-21 + href: https://www.legislation.gov.uk/ssi/2020/25/made + - title: SSI 2021/51, sums for 2021-22 + href: https://www.legislation.gov.uk/ssi/2021/51/made + - title: SSI 2022/52, sums for 2022-23 + href: https://www.legislation.gov.uk/ssi/2022/52/made + - title: SSI 2023/38, sums for 2023-24 + href: https://www.legislation.gov.uk/ssi/2023/38/made + - title: SSI 2024/35, sums for 2024-25 + href: https://www.legislation.gov.uk/ssi/2024/35/made + - title: SSI 2025/24, sums for 2025-26 + href: https://www.legislation.gov.uk/ssi/2025/24/made + - title: SSI 2026/56, sums for 2026-27 + href: https://www.legislation.gov.uk/ssi/2026/56/made diff --git a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/non_dep_deduction/exempt_universal_credit_without_earned_income.yaml b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/non_dep_deduction/exempt_universal_credit_without_earned_income.yaml new file mode 100644 index 0000000000..7ee2d4484a --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/non_dep_deduction/exempt_universal_credit_without_earned_income.yaml @@ -0,0 +1,13 @@ +description: Scotland Council Tax Reduction makes no deduction for a non-dependant entitled to Universal Credit calculated on the basis that they have no earned income, from the year this applies (SSI 2012/319 reg. 48(8)(c), SSI 2012/303 reg. 67(8)(c) and SSI 2021/249 reg. 90(8)(c)). +values: + 2013-04-01: false + 2016-04-01: true +metadata: + label: Scotland CTR exempts non-dependants on Universal Credit without earned income + unit: bool + period: year + reference: + - title: SSI 2012/319 reg. 48(8)(c) and SSI 2012/303 reg. 67(8)(c), inserted from 1 April 2016 by SSI 2016/81 + href: https://www.legislation.gov.uk/ssi/2016/81/made + - title: Council Tax Reduction (Scotland) Regulations 2021, reg. 90(8)(c) + href: https://www.legislation.gov.uk/ssi/2021/249/regulation/90 diff --git a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/non_dep_deduction/working_age_exempt_partner_of_income_related_benefit_claimant.yaml b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/non_dep_deduction/working_age_exempt_partner_of_income_related_benefit_claimant.yaml new file mode 100644 index 0000000000..d9f934cc99 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/non_dep_deduction/working_age_exempt_partner_of_income_related_benefit_claimant.yaml @@ -0,0 +1,17 @@ +description: Scotland's working-age Council Tax Reduction scheme makes no deduction for a non-dependant who is, or whose partner is, on Income Support, income-based JSA or income-related ESA and who is not on Universal Credit (a "qualifying income-related benefit claimant"), from the year this applies (SSI 2021/249 reg. 90(8)(a) with reg. 4(1)). Before April 2022 (SSI 2012/303 reg. 67(8)(a)), and in the pension-age scheme, only a non-dependant who is on one of these benefits is exempt. +values: + 2013-04-01: false + 2022-04-01: true +metadata: + label: Scotland working-age CTR exempts a non-dependant whose partner is on an income-related benefit + unit: bool + period: year + reference: + - title: Council Tax Reduction (Scotland) Regulations 2021, reg. 90(8)(a) + href: https://www.legislation.gov.uk/ssi/2021/249/regulation/90 + - title: Council Tax Reduction (Scotland) Regulations 2021, reg. 4(1) ("qualifying income-related benefit claimant") + href: https://www.legislation.gov.uk/ssi/2021/249/regulation/4 + - title: Council Tax Reduction (Scotland) Regulations 2021, reg. 1 (in force 1 April 2022) + href: https://www.legislation.gov.uk/ssi/2021/249/regulation/1 + - title: Council Tax Reduction (Scotland) Regulations 2012, reg. 67(8)(a) + href: https://www.legislation.gov.uk/ssi/2012/303/regulation/67 diff --git a/policyengine_uk/parameters/gov/local_authorities/wales/council_tax_reduction/non_dep_deduction/amount.yaml b/policyengine_uk/parameters/gov/local_authorities/wales/council_tax_reduction/non_dep_deduction/amount.yaml new file mode 100644 index 0000000000..72afa4a599 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/wales/council_tax_reduction/non_dep_deduction/amount.yaml @@ -0,0 +1,162 @@ +description: Wales Council Tax Reduction deducts this weekly amount for each non-dependant aged 18 or over, by the non-dependant's normal gross weekly income if they are in remunerative work; a non-dependant not in remunerative work pays the lowest amount. + +# Sums from paragraph 3 of Schedule 1 (pensioners) and paragraph 5 of Schedule +# 6 (persons who are not pensioners) to the Council Tax Reduction Schemes and +# Prescribed Requirements (Wales) Regulations 2013 (WSI 2013/3029); for +# 2013-14, the same paragraphs of WSI 2012/3144, as substituted for each +# financial year by the amending instruments in the references. Each year's +# sums apply from 1 April. +# 2016-17: the English text sets the first band at £194; the Welsh text has +# £195 (WSI 2017/46 reg. 3(a)(iii)-(v) replaced both). +brackets: +- threshold: + 2013-04-01: 0 + amount: + 2013-04-01: 3.65 + 2014-04-01: 3.75 + 2015-04-01: 3.9 + 2016-04-01: 4.05 + 2017-04-01: 4.2 + 2018-04-01: 4.35 + 2019-04-01: 4.55 + 2020-04-01: 4.85 + 2021-04-01: 5.1 + 2022-04-01: 5.3 + 2023-04-01: 5.45 + 2024-04-01: 5.8 + 2025-04-01: 6.25 + 2026-04-01: 6.7 +- threshold: + 2013-04-01: 186 + 2014-04-01: 188 + 2015-04-01: 189 + 2016-04-01: 194 + 2017-04-01: 200 + 2018-04-01: 205 + 2019-04-01: 210 + 2020-04-01: 217 + 2021-04-01: 217 + 2022-04-01: 224 + 2023-04-01: 236 + 2024-04-01: 256 + 2025-04-01: 266 + 2026-04-01: 279 + amount: + 2013-04-01: 7.25 + 2014-04-01: 7.5 + 2015-04-01: 7.8 + 2016-04-01: 8.1 + 2017-04-01: 8.4 + 2018-04-01: 8.7 + 2019-04-01: 9.15 + 2020-04-01: 9.75 + 2021-04-01: 10.2 + 2022-04-01: 10.6 + 2023-04-01: 10.9 + 2024-04-01: 11.55 + 2025-04-01: 12.45 + 2026-04-01: 13.35 +- threshold: + 2013-04-01: 322 + 2014-04-01: 326 + 2015-04-01: 328 + 2016-04-01: 338 + 2017-04-01: 346 + 2018-04-01: 355 + 2019-04-01: 365 + 2020-04-01: 377 + 2021-04-01: 377 + 2022-04-01: 389 + 2023-04-01: 410 + 2024-04-01: 445 + 2025-04-01: 463 + 2026-04-01: 485 + amount: + 2013-04-01: 9.15 + 2014-04-01: 9.45 + 2015-04-01: 9.85 + 2016-04-01: 10.25 + 2017-04-01: 10.6 + 2018-04-01: 10.95 + 2019-04-01: 11.5 + 2020-04-01: 12.25 + 2021-04-01: 12.85 + 2022-04-01: 13.35 + 2023-04-01: 13.7 + 2024-04-01: 14.5 + 2025-04-01: 15.65 + 2026-04-01: 16.8 +- threshold: + 2013-04-01: 401 + 2014-04-01: 406 + 2015-04-01: 408 + 2016-04-01: 420 + 2017-04-01: 430 + 2018-04-01: 440 + 2019-04-01: 450 + 2020-04-01: 469 + 2021-04-01: 469 + 2022-04-01: 484 + 2023-04-01: 511 + 2024-04-01: 554 + 2025-04-01: 577 + 2026-04-01: 605 + amount: + 2013-04-01: 10.95 + 2014-04-01: 11.3 + 2015-04-01: 11.75 + 2016-04-01: 12.25 + 2017-04-01: 12.7 + 2018-04-01: 13.1 + 2019-04-01: 13.75 + 2020-04-01: 14.65 + 2021-04-01: 15.35 + 2022-04-01: 15.95 + 2023-04-01: 16.4 + 2024-04-01: 17.35 + 2025-04-01: 18.7 + 2026-04-01: 20.05 +metadata: + label: Wales CTR non-dependant deduction + amount_unit: currency-GBP + threshold_unit: currency-GBP + period: week + type: single_amount + economy: false + propagate_metadata_to_children: true + uprating: gov.benefit_uprating_cpi + reference: + - title: Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2013, Sch. 1 para. 3 (pensioners) + href: https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/3 + - title: Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2013, Sch. 6 para. 5 (persons who are not pensioners) + href: https://www.legislation.gov.uk/wsi/2013/3029/schedule/6/paragraph/5 + - title: Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2012 (2013-14), Sch. 1 para. 3 and Sch. 6 para. 5 + href: https://www.legislation.gov.uk/wsi/2012/3144/schedule/1/paragraph/3 + - title: WSI 2013/112, sums for 2013-14 + href: https://www.legislation.gov.uk/wsi/2013/112/made + - title: WSI 2014/66, sums for 2014-15 + href: https://www.legislation.gov.uk/wsi/2014/66/made + - title: WSI 2015/44, sums for 2015-16 + href: https://www.legislation.gov.uk/wsi/2015/44/made + - title: WSI 2016/50, sums for 2016-17 + href: https://www.legislation.gov.uk/wsi/2016/50/made + - title: WSI 2017/46, sums for 2017-18 + href: https://www.legislation.gov.uk/wsi/2017/46/made + - title: WSI 2018/14, sums for 2018-19 + href: https://www.legislation.gov.uk/wsi/2018/14/made + - title: WSI 2019/11, sums for 2019-20 + href: https://www.legislation.gov.uk/wsi/2019/11/made + - title: WSI 2020/16, sums for 2020-21 + href: https://www.legislation.gov.uk/wsi/2020/16/made + - title: WSI 2021/34, sums for 2021-22 + href: https://www.legislation.gov.uk/wsi/2021/34/made + - title: WSI 2022/51, sums for 2022-23 + href: https://www.legislation.gov.uk/wsi/2022/51/made + - title: WSI 2023/47, sums for 2023-24 + href: https://www.legislation.gov.uk/wsi/2023/47/made + - title: WSI 2024/56, sums for 2024-25 + href: https://www.legislation.gov.uk/wsi/2024/56/made + - title: WSI 2025/59, sums for 2025-26 + href: https://www.legislation.gov.uk/wsi/2025/59/made + - title: WSI 2026/13, sums for 2026-27 + href: https://www.legislation.gov.uk/wsi/2026/13/made diff --git a/policyengine_uk/parameters/gov/local_authorities/wales/council_tax_reduction/non_dep_deduction/exempt_universal_credit_without_earned_income.yaml b/policyengine_uk/parameters/gov/local_authorities/wales/council_tax_reduction/non_dep_deduction/exempt_universal_credit_without_earned_income.yaml new file mode 100644 index 0000000000..d7ec14e179 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/wales/council_tax_reduction/non_dep_deduction/exempt_universal_credit_without_earned_income.yaml @@ -0,0 +1,11 @@ +description: Wales Council Tax Reduction makes no deduction for a non-dependant entitled to Universal Credit calculated on the basis that they have no earned income, from the year this applies (Sch. 1 para. 3(8)(c) and Sch. 6 para. 5(8)(c)). +values: + 2013-04-01: false + 2015-04-01: true +metadata: + label: Wales CTR exempts non-dependants on Universal Credit without earned income + unit: bool + period: year + reference: + - title: WSI 2013/3029 Sch. 1 para. 3(8)(c) and Sch. 6 para. 5(8)(c), added for 2015-16 by WSI 2015/44 + href: https://www.legislation.gov.uk/wsi/2015/44/made diff --git a/policyengine_uk/programs.yaml b/policyengine_uk/programs.yaml index ba571b4c2e..352e475e2b 100644 --- a/policyengine_uk/programs.yaml +++ b/policyengine_uk/programs.yaml @@ -586,7 +586,7 @@ programs: variable: council_tax_reduction parameter_prefix: gov.local_authorities verified_start_year: 2013 - notes: First-pass national support for Scotland, Wales, and England pensioner schemes (including the Pension Credit guarantee credit disregard and savings-credit-only rules), with reported CTB/CTR retained for unsupported English working-age local schemes. + notes: First-pass national support for Scotland, Wales, and England pensioner schemes (including the Pension Credit guarantee credit disregard and savings-credit-only rules, and each scheme's own non-dependant deductions and exemptions), with reported CTB/CTR retained for unsupported English working-age local schemes. # --- Treasury programs --- - id: cost_of_living_support diff --git a/policyengine_uk/tests/code_health/test_benefit_cap_lists_unique.py b/policyengine_uk/tests/code_health/test_benefit_cap_lists_unique.py new file mode 100644 index 0000000000..c4bb47b45b --- /dev/null +++ b/policyengine_uk/tests/code_health/test_benefit_cap_lists_unique.py @@ -0,0 +1,27 @@ +from collections import Counter + +import pytest + +from policyengine_uk.variables.gov.dwp.benefit_cap_welfare_benefits import ( + CLAIMANT_OR_PARTNER_BENEFITS, + FAMILY_BENEFITS, +) +from policyengine_uk.variables.gov.dwp.universal_credit.benefit_cap.uc_benefit_cap_welfare_benefits import ( + UC_FAMILY_BENEFITS, +) +from policyengine_uk.variables.gov.dwp.housing_benefit.non_dep_deduction._non_dependants import ( + FAMILY_GROSS_INCOME_BENEFITS, +) + + +@pytest.mark.parametrize( + "names", + [ + CLAIMANT_OR_PARTNER_BENEFITS + FAMILY_BENEFITS, + CLAIMANT_OR_PARTNER_BENEFITS + UC_FAMILY_BENEFITS, + FAMILY_GROSS_INCOME_BENEFITS, + ], + ids=["housing-benefit-cap", "universal-credit-cap", "non-dependant-family-income"], +) +def test_each_name_occurs_once(names): + assert not {name: n for name, n in Counter(names).items() if n != 1} diff --git a/policyengine_uk/tests/fixtures/council_tax_reduction_non_dependant_deductions.yaml b/policyengine_uk/tests/fixtures/council_tax_reduction_non_dependant_deductions.yaml new file mode 100644 index 0000000000..2aa4334b76 --- /dev/null +++ b/policyengine_uk/tests/fixtures/council_tax_reduction_non_dependant_deductions.yaml @@ -0,0 +1,185 @@ +# Council Tax Reduction non-dependant deductions as enacted, weekly, for the +# financial year starting 1 April of each year, from each year's amending +# instrument (source) and the point-in-time text of the scheme provision on +# legislation.gov.uk. Bands include their lower edge; the first amount is +# para (1)(b), the last para (1)(a). Wales: WSI 2012/3144 (2013-14), then WSI +# 2013/3029 Sch 1 para 3 and Sch 6 para 5 (identical scales). Scotland: SSI +# 2012/319 reg 48, SSI 2012/303 reg 67 and SSI 2021/249 reg 90 (identical). + +england_pensioners: + parameter: gov.local_authorities.england.council_tax_reduction.pensioners.non_dep_deduction.amount + years: + 2013: + thresholds: [0, 186.00, 322.00, 401.00] + amounts: [3.65, 7.25, 9.15, 10.95] + source: https://www.legislation.gov.uk/uksi/2012/3085/made + 2014: + thresholds: [0, 188.00, 326.00, 406.00] + amounts: [3.70, 7.45, 9.40, 11.25] + source: https://www.legislation.gov.uk/uksi/2013/3181/made + 2015: + thresholds: [0, 189.00, 328.00, 408.00] + amounts: [3.74, 7.52, 9.49, 11.36] + source: https://www.legislation.gov.uk/uksi/2014/3312/made + 2016: + thresholds: [0, 195.00, 338.00, 420.00] + amounts: [3.77, 7.58, 9.56, 11.45] + source: https://www.legislation.gov.uk/uksi/2015/2041/made + 2017: + thresholds: [0, 196.95, 341.40, 424.20] + amounts: [3.80, 7.65, 9.65, 11.55] + source: https://www.legislation.gov.uk/uksi/2016/1262/made + 2018: + thresholds: [0, 202.85, 351.65, 436.90] + amounts: [3.90, 7.90, 9.95, 11.90] + source: https://www.legislation.gov.uk/uksi/2017/1305/made + 2019: + thresholds: [0, 207.70, 360.10, 447.40] + amounts: [4.00, 8.10, 10.20, 12.20] + source: https://www.legislation.gov.uk/uksi/2018/1346/made + 2020: + thresholds: [0, 217.00, 377.00, 469.00] + amounts: [4.05, 8.25, 10.35, 12.40] + source: https://www.legislation.gov.uk/uksi/2020/23/made + 2021: + thresholds: [0, 217.00, 377.00, 469.00] + amounts: [4.05, 8.30, 10.40, 12.45] + source: https://www.legislation.gov.uk/uksi/2021/29/made + 2022: + thresholds: [0, 224.00, 389.00, 484.00] + amounts: [4.20, 8.55, 10.70, 12.85] + source: https://www.legislation.gov.uk/uksi/2022/25/made + 2023: + thresholds: [0, 236.00, 410.00, 511.00] + amounts: [4.60, 9.40, 11.80, 14.15] + source: https://www.legislation.gov.uk/uksi/2023/16/made + 2024: + thresholds: [0, 256.00, 445.00, 554.00] + amounts: [4.90, 10.05, 12.60, 15.10] + source: https://www.legislation.gov.uk/uksi/2024/29/made + 2025: + thresholds: [0, 266.00, 463.00, 577.00] + amounts: [5.00, 10.20, 12.80, 15.35] + source: https://www.legislation.gov.uk/uksi/2025/39/made + 2026: + thresholds: [0, 279.00, 485.00, 605.00] + amounts: [5.20, 10.60, 13.30, 15.95] + source: https://www.legislation.gov.uk/uksi/2026/27/made +wales: + parameter: gov.local_authorities.wales.council_tax_reduction.non_dep_deduction.amount + years: + 2013: + thresholds: [0, 186.00, 322.00, 401.00] + amounts: [3.65, 7.25, 9.15, 10.95] + source: https://www.legislation.gov.uk/wsi/2013/112/made + 2014: + thresholds: [0, 188.00, 326.00, 406.00] + amounts: [3.75, 7.50, 9.45, 11.30] + source: https://www.legislation.gov.uk/wsi/2014/66/made + 2015: + thresholds: [0, 189.00, 328.00, 408.00] + amounts: [3.90, 7.80, 9.85, 11.75] + source: https://www.legislation.gov.uk/wsi/2015/44/made + 2016: + thresholds: [0, 194.00, 338.00, 420.00] + amounts: [4.05, 8.10, 10.25, 12.25] + source: https://www.legislation.gov.uk/wsi/2016/50/made + 2017: + thresholds: [0, 200.00, 346.00, 430.00] + amounts: [4.20, 8.40, 10.60, 12.70] + source: https://www.legislation.gov.uk/wsi/2017/46/made + 2018: + thresholds: [0, 205.00, 355.00, 440.00] + amounts: [4.35, 8.70, 10.95, 13.10] + source: https://www.legislation.gov.uk/wsi/2018/14/made + 2019: + thresholds: [0, 210.00, 365.00, 450.00] + amounts: [4.55, 9.15, 11.50, 13.75] + source: https://www.legislation.gov.uk/wsi/2019/11/made + 2020: + thresholds: [0, 217.00, 377.00, 469.00] + amounts: [4.85, 9.75, 12.25, 14.65] + source: https://www.legislation.gov.uk/wsi/2020/16/made + 2021: + thresholds: [0, 217.00, 377.00, 469.00] + amounts: [5.10, 10.20, 12.85, 15.35] + source: https://www.legislation.gov.uk/wsi/2021/34/made + 2022: + thresholds: [0, 224.00, 389.00, 484.00] + amounts: [5.30, 10.60, 13.35, 15.95] + source: https://www.legislation.gov.uk/wsi/2022/51/made + 2023: + thresholds: [0, 236.00, 410.00, 511.00] + amounts: [5.45, 10.90, 13.70, 16.40] + source: https://www.legislation.gov.uk/wsi/2023/47/made + 2024: + thresholds: [0, 256.00, 445.00, 554.00] + amounts: [5.80, 11.55, 14.50, 17.35] + source: https://www.legislation.gov.uk/wsi/2024/56/made + 2025: + thresholds: [0, 266.00, 463.00, 577.00] + amounts: [6.25, 12.45, 15.65, 18.70] + source: https://www.legislation.gov.uk/wsi/2025/59/made + 2026: + thresholds: [0, 279.00, 485.00, 605.00] + amounts: [6.70, 13.35, 16.80, 20.05] + source: https://www.legislation.gov.uk/wsi/2026/13/made +scotland: + parameter: gov.local_authorities.scotland.council_tax_reduction.non_dep_deduction.amount + years: + 2013: + thresholds: [0, 186.00, 322.00, 401.00] + amounts: [3.65, 7.25, 9.15, 10.95] + source: https://www.legislation.gov.uk/ssi/2013/49/made + 2014: + thresholds: [0, 188.00, 326.00, 406.00] + amounts: [3.75, 7.50, 9.50, 11.35] + source: https://www.legislation.gov.uk/ssi/2014/35/made + 2015: + thresholds: [0, 189.00, 328.00, 408.00] + amounts: [3.85, 7.70, 9.80, 11.70] + source: https://www.legislation.gov.uk/ssi/2015/46/made + 2016: + thresholds: [0, 195.00, 338.00, 420.00] + amounts: [3.90, 7.75, 9.85, 11.80] + source: https://www.legislation.gov.uk/ssi/2016/81/made + 2017: + thresholds: [0, 200.00, 346.00, 430.00] + amounts: [3.95, 7.85, 9.95, 11.90] + source: https://www.legislation.gov.uk/ssi/2017/41/made + 2018: + thresholds: [0, 204.00, 354.00, 439.00] + amounts: [4.05, 8.10, 10.25, 12.25] + source: https://www.legislation.gov.uk/ssi/2018/69/made + 2019: + thresholds: [0, 209.00, 363.00, 451.00] + amounts: [4.15, 8.30, 10.50, 12.55] + source: https://www.legislation.gov.uk/ssi/2019/29/made + 2020: + thresholds: [0, 213.00, 370.00, 458.00] + amounts: [4.25, 8.45, 10.70, 12.80] + source: https://www.legislation.gov.uk/ssi/2020/25/made + 2021: + thresholds: [0, 213.00, 370.00, 458.00] + amounts: [4.30, 8.50, 10.80, 12.90] + source: https://www.legislation.gov.uk/ssi/2021/51/made + 2022: + thresholds: [0, 228.00, 396.00, 490.00] + amounts: [4.45, 8.80, 11.15, 13.30] + source: https://www.legislation.gov.uk/ssi/2022/52/made + 2023: + thresholds: [0, 241.00, 418.00, 517.00] + amounts: [4.90, 9.70, 12.30, 14.65] + source: https://www.legislation.gov.uk/ssi/2023/38/made + 2024: + thresholds: [0, 260.00, 451.00, 558.00] + amounts: [5.25, 10.35, 13.15, 15.65] + source: https://www.legislation.gov.uk/ssi/2024/35/made + 2025: + thresholds: [0, 273.00, 474.00, 586.00] + amounts: [5.35, 10.55, 13.40, 15.95] + source: https://www.legislation.gov.uk/ssi/2025/24/made + 2026: + thresholds: [0, 286.00, 497.00, 614.00] + amounts: [5.55, 10.95, 13.90, 16.55] + source: https://www.legislation.gov.uk/ssi/2026/56/made 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 79162f0948..e1f7891896 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 @@ -3,9 +3,9 @@ # 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. +# 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: @@ -71,8 +71,11 @@ is_responsible_for_child_or_qualifying_young_person_for_universal_credit: false lha_renter_has_non_dependant: true 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 + # No UC QYP (UC Regs 2013 reg 5(3): training under a contract of + # employment does not count), but a non-dependant blocks A. The + # non-dependant is not a child, so has a bedroom (Sch 4 para 10(1)(c)): + # 1 + 1 = 2: C. + LHA_category: C # 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 @@ -98,8 +101,9 @@ is_responsible_for_child_or_qualifying_young_person_for_universal_credit: true lha_renter_has_non_dependant: false is_lha_shared_accommodation_rate_specified_renter: false - # Responsibility for a UC QYP blocks A. Existing bedroom formula = 1: B. - LHA_category: B + # Responsibility for a UC QYP blocks A. The QYP has a bedroom (Sch 4 + # para 10(1)(b)): 1 + 1 = 2: C. + LHA_category: C # 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 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_young_person_bedroom.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_young_person_bedroom.yaml new file mode 100644 index 0000000000..8d3ae56336 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/LHA_young_person_bedroom.yaml @@ -0,0 +1,514 @@ +# A member of the benefit unit aged 16 or over who is not the claimant or +# partner has their own bedroom in the size criteria, with the exceptions below. +# +# Universal Credit: UC Regs 2013 Sch 4 para 10(1)(b), "a qualifying young +# person for whom the renter or either joint renter is responsible"; anyone +# else aged 16 or over living with the renter is a non-dependant (para 9(2)) +# with a bedroom under para 10(1)(c), "a non-dependant who is not a child", +# except a qualifying young person no one is responsible for (para 9(2)(g)) +# and a member who heads the household and is not a qualifying young person +# the renter is responsible for, whom the formula treats as liable for the +# rent (para 9(2)(d)). +# A child is a person under 16 (WRA 2012 s.40). +# +# Housing Benefit: HB Regs 2006 and HB (SPC) Regs 2006 reg 13D(3)(b), "a +# person who is not a child". +# +# Rents of £200 a week (£10,400 a year) sit between the model's Maidstone +# one-bedroom (B, £173.08 a week) and two-bedroom (C, £208.85 a week) rates, +# so the bedroom decides whether the rent is met in full. + +- name: A qualifying young person has their own bedroom + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_household_head: true + is_parent: true + young_person: + age: 17 + current_education: UPPER_SECONDARY + benunits: + family: + members: [parent, young_person] + households: + home: + members: [parent, young_person] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_claimant_or_partner: [true, false] + is_child_or_qualifying_young_person_for_universal_credit: [false, true] + # UC: the renter (para 10(1)(a)) and the qualifying young person (para + # 10(1)(b)): 2 bedrooms, category C. + LHA_allowed_bedrooms: 2 + LHA_category: C + # The rent is below the two-bedroom rate, so it is met in full. + uc_housing_costs_element: 10_400 + # HB: one bedroom each for the claimant and the young person, neither of + # whom is a child (reg 13D(3)(b)): 2 bedrooms, category C. + housing_benefit_LHA_allowed_bedrooms: 2 + housing_benefit_LHA_category: C + +- name: A qualifying young person aged 19 has their own bedroom + period: 2026 + input: + people: + parent: + age: 50 + is_household_head: true + is_parent: true + young_person: + age: 19 + current_education: UPPER_SECONDARY + # UC Regs 2013 reg 5(1)(b) and 5(2): enrolled before 19, and before + # the 1 September after their 19th birthday. + age_started_or_accepted_current_education_or_training: 18 + is_before_universal_credit_qualifying_young_person_terminal_date: true + benunits: + family: + members: [parent, young_person] + households: + home: + members: [parent, young_person] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_claimant_or_partner: [true, false] + is_child_or_qualifying_young_person_for_universal_credit: [false, true] + LHA_allowed_bedrooms: 2 + LHA_category: C + uc_housing_costs_element: 10_400 + housing_benefit_LHA_allowed_bedrooms: 2 + housing_benefit_LHA_category: C + +- name: A member aged 18 who is not a qualifying young person is a non-dependant with a bedroom + period: 2026 + input: + people: + parent: + age: 45 + is_household_head: true + is_parent: true + son: + age: 18 + current_education: NOT_IN_EDUCATION + benunits: + family: + members: [parent, son] + households: + home: + members: [parent, son] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_claimant_or_partner: [true, false] + is_qualifying_young_person_for_universal_credit: [false, false] + # UC: not a qualifying young person, so a non-dependant (para 9(2)) who + # is not a child (para 10(1)(c)). + LHA_allowed_bedrooms: 2 + LHA_category: C + # HB: a person who is not a child (reg 13D(3)(b)). + housing_benefit_LHA_allowed_bedrooms: 2 + housing_benefit_LHA_category: C + +- name: A qualifying young person no one is responsible for has no Universal Credit bedroom + period: 2026 + input: + people: + carer: + age: 40 + is_household_head: true + is_parent: true + young_person: + age: 17 + current_education: UPPER_SECONDARY + # UC Regs 2013 reg 4(6)(a): no one is responsible for a qualifying + # young person looked after by a local authority. + is_looked_after_by_local_authority: true + benunits: + family: + members: [carer, young_person] + households: + home: + members: [carer, young_person] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_qualifying_young_person_for_universal_credit: [false, true] + is_child_or_qualifying_young_person_for_universal_credit: [false, false] + # Not under para 10(1)(b), and not a non-dependant (para 9(2)(g)): the + # renter's bedroom only. + LHA_allowed_bedrooms: 1 + LHA_category: B + +- name: A qualifying young person cannot share with a younger sibling of the same sex + period: 2026 + input: + people: + parent_1: + age: 42 + is_household_head: true + is_parent: true + is_male: true + parent_2: + age: 40 + is_parent: true + is_male: false + young_person: + age: 17 + is_male: true + current_education: UPPER_SECONDARY + boy: + age: 12 + is_male: true + girl: + age: 8 + is_male: false + benunits: + family: + members: [parent_1, parent_2, young_person, boy, girl] + households: + home: + members: [parent_1, parent_2, young_person, boy, girl] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_claimant_or_partner: [true, true, false, false, false] + # UC: the couple (para 10(1)(a)), the qualifying young person (b), and + # one bedroom each for the boy and the girl, who are of opposite sexes + # and one of whom is 10 or over ((d)-(f)): 4 bedrooms, category E. + LHA_allowed_bedrooms: 4 + LHA_category: E + # HB: the couple (reg 13D(3)(a)), the young person ((b)), and the two + # children ((e)): 4 bedrooms, the maximum category. + housing_benefit_LHA_allowed_bedrooms: 4 + housing_benefit_LHA_category: E + +- name: A partner aged 19 adds no bedroom + period: 2026 + input: + people: + claimant: + age: 24 + is_household_head: true + partner: + age: 19 + current_education: NOT_IN_EDUCATION + benunits: + couple: + members: [claimant, partner] + households: + home: + members: [claimant, partner] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_claimant_or_partner: [true, true] + # The couple share one bedroom (UC para 10(1)(a); HB reg 13D(3)(a)). + LHA_allowed_bedrooms: 1 + LHA_category: B + housing_benefit_LHA_allowed_bedrooms: 1 + housing_benefit_LHA_category: B + +- name: A pensioner couple's grandchild in full-time education has their own Housing Benefit bedroom + period: 2026 + absolute_error_margin: 0.01 + input: + people: + grandparent_1: + age: 70 + is_household_head: true + grandparent_2: + age: 68 + grandchild: + age: 17 + current_education: UPPER_SECONDARY + benunits: + family: + members: [grandparent_1, grandparent_2, grandchild] + households: + home: + members: [grandparent_1, grandparent_2, grandchild] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_claimant_or_partner: [true, true, false] + is_child_or_young_person_for_legacy_benefits: [false, false, true] + # HB (SPC) Regs 2006 reg 13D(3): the couple ((a)) and the young person + # ((b)): 2 bedrooms, category C. + housing_benefit_LHA_allowed_bedrooms: 2 + housing_benefit_LHA_category: C + # No income, so Housing Benefit meets the eligible rent: the rent, which + # is below the two-bedroom rate. + housing_benefit: 10_400 + +- name: A qualifying young person no one is responsible for in a non-dependant's family adds no Universal Credit bedroom + period: 2026 + input: + people: + renter: + age: 40 + is_household_head: true + carer: + age: 45 + is_parent: true + young_person: + age: 17 + current_education: UPPER_SECONDARY + is_looked_after_by_local_authority: true + benunits: + renter_family: + members: [renter] + carer_family: + members: [carer, young_person] + households: + home: + members: [renter, carer, young_person] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_non_dependant_of_household_head: [false, true, true] + is_qualifying_young_person_for_universal_credit: [false, false, true] + is_child_or_qualifying_young_person_for_universal_credit: [false, false, false] + # The renter (para 10(1)(a)) and the carer, a non-dependant who is not a + # child ((c)). The young person is no one's responsibility (reg 4(6)), so + # is not a non-dependant (para 9(2)(g)): 2 bedrooms, category C. + LHA_allowed_bedrooms: [2, 1] + LHA_category: [C, B] + +- name: A non-dependant couple has two Universal Credit bedrooms but one Housing Benefit bedroom + period: 2026 + input: + people: + renter: + age: 40 + is_household_head: true + non_dependant_1: + age: 30 + non_dependant_2: + age: 28 + benunits: + renter_family: + members: [renter] + couple: + members: [non_dependant_1, non_dependant_2] + households: + home: + members: [renter, non_dependant_1, non_dependant_2] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_claimant_or_partner: [true, true, true] + # UC: the renter (para 10(1)(a)) and each non-dependant who is not a + # child ((c)): 3 bedrooms, category D. The couple's own category would be + # 1 bedroom, and a couple is not a specified renter (para 28): B. + LHA_allowed_bedrooms: [3, 1] + LHA_category: [D, B] + # HB: the claimant ((b)) and the occupier couple ((a)): 2 bedrooms, + # category C. An ordinary couple shares a bedroom; only a couple who + # cannot share has two ((za); DWP circular HB A3/2017). The couple pay no + # rent; their own category would be 1 bedroom: B. + housing_benefit_LHA_allowed_bedrooms: [2, 1] + housing_benefit_LHA_category: [C, B] + +- name: A member who heads the household but does not claim adds no Universal Credit bedroom + period: 2025 + input: + people: + mother: + age: 45 + is_parent: true + son: + age: 20 + current_education: NOT_IN_EDUCATION + is_household_head: true + benunits: + family: + members: [mother, son] + households: + home: + members: [mother, son] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_resolved_household_head: [false, true] + is_claimant_or_partner: [true, false] + is_qualifying_young_person_for_universal_credit: [false, false] + # The formula reads the son, as household head, as the householder in + # whose name the home is rented. He does not claim, so he is not a + # renter (Sch 4 paras 1(2) and 9(1)(a)). Aged 20 and not in education, he + # is not a qualifying young person (para 9(1)(b)). Liable for his + # occupation, he is not a non-dependant (para 9(2)(d)). Only the renter's + # bedroom counts (para 10(1)(a)): 1 bedroom, category B. + LHA_allowed_bedrooms: 1 + LHA_category: B + +- name: A household head who is a qualifying young person the renter is responsible for has their own bedroom + period: 2025 + absolute_error_margin: 0.01 + input: + people: + mother: + age: 45 + is_male: false + is_parent: true + daughter: + age: 17 + is_male: false + current_education: UPPER_SECONDARY + is_household_head: true + benunits: + family: + members: [mother, daughter] + households: + home: + members: [mother, daughter] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_resolved_household_head: [false, true] + is_claimant_or_partner: [true, false] + is_child_or_qualifying_young_person_for_universal_credit: [false, true] + # The daughter heads the household but is a qualifying young person her + # mother is responsible for, so she is in the extended benefit unit + # (Sch 4 para 9(1)(b)) whoever is liable for the rent. The renter + # (para 10(1)(a)) and the qualifying young person (para 10(1)(b)): + # 2 bedrooms, category C, and the rent is met in full. + LHA_allowed_bedrooms: 2 + LHA_category: C + uc_housing_costs_element: 10_400 + # HB: one bedroom each for the claimant and the daughter, an occupier + # who is not a child (reg 13D(3)(b)). + housing_benefit_LHA_allowed_bedrooms: 2 + housing_benefit_LHA_category: C + +# A non-dependant in the renter's benefit unit has a bedroom (para 10(1)(c)) +# and bears a housing cost contribution (para 13). The claim that bears the +# contribution has the bedroom, and coding the same adult in a benefit unit +# of their own changes no claim. +# +# 2026-27 amounts in the parameter files: UC standard allowance for a single +# claimant aged 25 or over £424.90 a month, £5,098.80 a year; housing cost +# contribution £96.55 a month, £1,158.60 a year, for a non-dependant aged 21 +# or over (paras 14(1), 16(2)(a)); Household Energy Payment £100 (council +# tax bands A to D in England; the default band is D); TV Licence £180. + +- name: An adult son in the renter's benefit unit has a bedroom and bears a housing cost contribution + period: 2026 + absolute_error_margin: 0.01 + input: + people: + mother: + age: 45 + is_household_head: true + son: + age: 22 + current_education: NOT_IN_EDUCATION + benunits: + family: + members: [mother, son] + households: + home: + members: [mother, son] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_benefit_unit_non_dependant_for_universal_credit: [false, true] + # The renter and the son, a non-dependant who is not a child: 2 bedrooms, + # category C (£208.85 a week, £10,860.20 a year, above the rent). + LHA_allowed_bedrooms: 2 + LHA_category: C + # 96.55 x 12 = 1,158.60. + uc_non_dep_deductions: 1_158.60 + # 10,400 - 1,158.60 = 9,241.40. + uc_housing_costs_element: 9_241.40 + # 5,098.80 + 9,241.40 = 14,340.20, with no income to taper. + universal_credit: 14_340.20 + # 14,340.20 + 100 (Household Energy Payment) - 180 (TV Licence). + household_net_income: 14_260.20 + +- name: The same adult son in a benefit unit of his own, not claiming, leaves his mother's claim unchanged + period: 2026 + absolute_error_margin: 0.01 + input: + people: + mother: + age: 45 + is_household_head: true + son: + age: 22 + current_education: NOT_IN_EDUCATION + benunits: + family: + members: [mother] + son_unit: + members: [son] + would_claim_uc: false + households: + home: + members: [mother, son] + rent: 10_400 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_non_dependant_of_household_head: [false, true] + # The renter and the son, now another family's non-dependant: 2 + # bedrooms, category C, as when he is in her benefit unit. + LHA_allowed_bedrooms: [2, 1] + LHA_category: [C, A] + uc_non_dep_deductions: [1_158.60, 0] + uc_housing_costs_element: [9_241.40, 0] + universal_credit: [14_340.20, 0] + household_net_income: 14_260.20 + +- name: A claiming joint tenant has the bedroom of the non-claiming head's adult son whose contribution it bears + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + # Over State Pension age, so not eligible for Universal Credit. + age: 80 + is_household_head: true + is_claimant_or_partner: true + son: + age: 30 + is_claimant_or_partner: false + sharer: + age: 40 + is_claimant_or_partner: true + benunits: + head_family: + members: [head, son] + sharer_family: + members: [sharer] + liable_for_share_of_household_rent: true + households: + home: + members: [head, son, sharer] + rent: 12_000 + tenure_type: RENT_PRIVATELY + brma: MAIDSTONE + output: + is_uc_eligible: [false, true] + uc_non_dependants_counted: [false, true] + # The head's family makes no Universal Credit claim, so the sharer's + # claim counts the son (para 9(2)(f)): the sharer bears his contribution + # (96.55 x 12 = 1,158.60) and has his bedroom (renter 1 + son 1 = 2). + uc_non_dep_deductions: [0, 1_158.60] + LHA_allowed_bedrooms: [2, 2] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/benefit_unit_non_dependants.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/benefit_unit_non_dependants.yaml new file mode 100644 index 0000000000..1edd5322e9 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/benefit_unit_non_dependants.yaml @@ -0,0 +1,1363 @@ +# Non-dependants within the claimant's own benefit unit. +# +# A benefit unit can include someone aged 16 or over who is neither the +# claimant or partner nor a child or young person the claimant is responsible +# for, such as an adult son or a 19-year-old who has left education. In law +# that person is a non-dependant of the renter (UC Regs 2013 Sch 4 para +# 9(1)(c) and (2); HB Regs 2006 and HB (SPC) Regs 2006 reg 3; CTR SI +# 2012/2885 reg 9, WSI 2013/3029 reg 9, SSI 2021/249 reg 8), so their family's +# claim bears a deduction for them, subject to each scheme's exemptions. +# +# Amounts for 2026-27: +# - UC housing cost contribution £96.55 a month (Sch 4 para 14(1)), aged 21 +# or over (para 16(2)(a)): £1,158.60 a year. +# - HB non-dependant aged 18 or over not in remunerative work £20.40 a week +# (reg 74(1)(b)): £1,060.80 a year. In remunerative work with gross income +# of £300 a week: £64.35 (reg 74(2)(c)), £3,346.20 a year. The HB cases +# give part-time earners £50 a week for 10 hours, below remunerative work +# (16 hours, reg 6) and inside the lowest band. + +- name: UC, an adult son in the renter's benefit unit bears a housing cost contribution + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, son] + households: + household: + members: [parent, son] + output: + is_benefit_unit_non_dependant_for_universal_credit: [false, true] + uc_individual_non_dep_deduction: [0, 1_158.60] + uc_non_dep_deductions: 1_158.60 + +- name: UC, no contribution for a non-dependant in the benefit unit under 21 + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + # Left education: not a qualifying young person. + age: 19 + is_claimant_or_partner: false + is_qualifying_young_person_for_universal_credit: false + benunits: + benunit: + members: [parent, son] + households: + household: + members: [parent, son] + output: + is_benefit_unit_non_dependant_for_universal_credit: [false, true] + uc_non_dep_deductions: 0 + +- name: UC, a qualifying young person the renter is responsible for is not a non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + daughter: + age: 19 + is_claimant_or_partner: false + is_qualifying_young_person_for_universal_credit: true + benunits: + benunit: + members: [parent, daughter] + households: + household: + members: [parent, daughter] + output: + is_benefit_unit_non_dependant_for_universal_credit: [false, false] + uc_non_dep_deductions: 0 + +# Sch 4 para 9(2)(g): a qualifying young person for whom no one in the +# renter's extended benefit unit is responsible is not a non-dependant. +- name: UC, a qualifying young person looked after by a local authority is not a non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 50 + is_household_head: true + is_claimant_or_partner: true + young_person: + age: 17 + is_claimant_or_partner: false + is_qualifying_young_person_for_universal_credit: true + is_looked_after_by_local_authority: true + benunits: + benunit: + members: [carer, young_person] + households: + household: + members: [carer, young_person] + output: + is_child_or_qualifying_young_person_for_universal_credit: [false, false] + is_benefit_unit_non_dependant_for_universal_credit: [false, false] + uc_non_dep_deductions: 0 + +# Sch 4 para 15: no contribution for any non-dependant if the renter, or +# either joint renter of a couple, receives the PIP daily living component. +- name: UC, no contribution where the renter receives the PIP daily living component + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + pip_dl: 3_000 + son: + age: 25 + is_claimant_or_partner: false + aunt: + # A non-dependant in another family. + age: 30 + benunits: + benunit_1: + members: [parent, son] + benunit_2: + members: [aunt] + households: + household: + members: [parent, son, aunt] + output: + uc_non_dep_deductions_renter_exempt: [true, false] + uc_non_dep_deductions: [0, 0] + +- name: UC, no contribution where the renter's partner is blind + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent_1: + age: 52 + is_household_head: true + is_claimant_or_partner: true + parent_2: + age: 50 + is_claimant_or_partner: true + is_blind: true + son: + age: 25 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent_1, parent_2, son] + households: + household: + members: [parent_1, parent_2, son] + output: + uc_non_dep_deductions: 0 + +# Para 15(1)(a) exempts "any renter who is a single person to whom +# sub-paragraph (2) applies": the renter's own disability, not a child's. +- name: UC, a disabled child in the renter's family does not exempt the renter + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + child: + age: 10 + is_claimant_or_partner: false + dla_sc_category: MIDDLE + benunits: + benunit: + members: [parent, son, child] + households: + household: + members: [parent, son, child] + output: + # A child under 16 is never a non-dependant (WRA 2012 s.40). + is_benefit_unit_non_dependant_for_universal_credit: [false, true, false] + is_benefit_unit_non_dependant_for_legacy_benefits: [false, true, false] + uc_non_dep_deductions_renter_exempt: false + uc_non_dep_deductions: 1_158.60 + +# Sch 4 para 16(2)(d): no contribution for a non-dependant receiving +# Attendance Allowance. +- name: UC, no contribution for a non-dependant in the benefit unit receiving Attendance Allowance + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + attendance_allowance: 3_000 + benunits: + benunit: + members: [parent, son] + households: + household: + members: [parent, son] + output: + uc_non_dep_deduction_exempt: [false, true] + uc_non_dep_deductions: 0 + +# Pension Credit is the claimant's award: an adult son in a family on Pension +# Credit is not receiving it, so para 16(2)(b) does not exempt him. +- name: UC, a family's Pension Credit does not exempt the non-dependant in its benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 70 + is_claimant_or_partner: true + son: + age: 40 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, son] + pension_credit: 2_000 + output: + uc_non_dep_deduction_exempt: [true, false] + +- name: UC, the head's family bears non-dependants in its own benefit unit and in other families + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + aunt: + age: 60 + is_claimant_or_partner: true + benunits: + benunit_1: + members: [parent, son] + benunit_2: + members: [aunt] + households: + household: + members: [parent, son, aunt] + output: + uc_non_dep_deductions: [2_317.20, 0] + +# A non-dependant family's own adult member is a non-dependant of the +# household head, counted once, in the head's claim (Sch 4 para 9(2)(f)). The +# head is below State Pension age, so has a Universal Credit claim to count +# them in (uc_non_dependants_counted). +- name: UC, an adult in a non-dependant family is the household head's non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 60 + is_household_head: true + is_claimant_or_partner: true + daughter: + age: 35 + is_claimant_or_partner: true + grandson: + age: 23 + is_claimant_or_partner: false + benunits: + benunit_1: + members: [head] + benunit_2: + members: [daughter, grandson] + households: + household: + members: [head, daughter, grandson] + output: + uc_non_dep_deductions: [2_317.20, 0] + +# A sharer of the rent bears the non-dependant in its own benefit unit; the +# household head's family does not (para 9(2)(f)). +- name: UC, a sharer of the rent bears the non-dependant in its own benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + sharer: + age: 48 + is_claimant_or_partner: true + sharer_son: + age: 26 + is_claimant_or_partner: false + benunits: + benunit_1: + members: [head] + benunit_2: + members: [sharer, sharer_son] + liable_for_share_of_household_rent: true + households: + household: + members: [head, sharer, sharer_son] + rent: 12_000 + output: + uc_non_dep_deductions: [0, 1_158.60] + +- name: UC, the housing costs element nets off the contribution for an adult son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, son] + benunit_tenure_type: RENT_FROM_COUNCIL + benunit_rent: 6_000 + households: + household: + members: [parent, son] + output: + # £6,000 - £1,158.60. + uc_housing_costs_element: 4_841.40 + +- name: HB, an adult son in the claimant's benefit unit working part time + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 2_600 + weekly_hours: 10 + benunits: + benunit: + members: [parent, son] + households: + household: + members: [parent, son] + output: + is_benefit_unit_non_dependant_for_legacy_benefits: [false, true] + housing_benefit_non_dep_deductions: 1_060.80 + +- name: HB, an adult daughter in the claimant's benefit unit working full time on £300 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + daughter: + age: 22 + is_claimant_or_partner: false + employment_income: 15_600 + weekly_hours: 37.5 + benunits: + benunit: + members: [parent, daughter] + households: + household: + members: [parent, daughter] + output: + housing_benefit_non_dep_deductions: 3_346.20 + +- name: HB, no deduction for a non-dependant in the benefit unit under 18 + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 17 + is_claimant_or_partner: false + is_child_or_qualifying_young_person_for_child_benefit: false + benunits: + benunit: + members: [parent, son] + households: + household: + members: [parent, son] + output: + is_benefit_unit_non_dependant_for_legacy_benefits: [false, true] + housing_benefit_non_dep_deductions: 0 + +# HB reg 19: a Child Benefit qualifying young person is in the claimant's +# family, not a non-dependant. +- name: HB, a young person in the claimant's family is not a non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + daughter: + age: 19 + is_claimant_or_partner: false + is_child_or_qualifying_young_person_for_child_benefit: true + benunits: + benunit: + members: [parent, daughter] + households: + household: + members: [parent, daughter] + output: + is_benefit_unit_non_dependant_for_legacy_benefits: [false, false] + housing_benefit_non_dep_deductions: 0 + +# A joint tenant's adult son normally resides with both joint tenants (reg +# 3(1) and (4)) and is in neither's family under reg 3(2)(a), so he is a +# non-dependant of each, apportioned by their shares of the rent (reg 74(5)). +- name: HB, joint tenants share the deduction for one tenant's adult son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 2_600 + weekly_hours: 10 + sharer: + age: 48 + is_claimant_or_partner: true + benunits: + benunit_1: + members: [head, son] + benunit_2: + members: [sharer] + liable_for_share_of_household_rent: true + households: + household: + members: [head, son, sharer] + rent: 12_000 + output: + share_of_household_rent: [0.5, 0.5] + housing_benefit_non_dep_deductions: [530.40, 530.40] + +# The same son coded as his own benefit unit gives the same deductions: the +# result does not depend on how the survey groups him. +- name: HB, joint tenants share the deduction for an adult son in his own benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 10 + sharer: + age: 48 + is_claimant_or_partner: true + benunits: + benunit_1: + members: [head] + benunit_2: + members: [sharer] + liable_for_share_of_household_rent: true + benunit_3: + members: [son] + households: + household: + members: [head, son, sharer] + rent: 12_000 + output: + housing_benefit_non_dep_deductions: [530.40, 530.40, 0] + +# A lodger's adult son is the lodger's non-dependant, and the householder's +# too: reg 3(2)(e)(i) excludes only the person liable to pay the householder. +# The lodger is not a joint occupier with the householder, so reg 74(5) does +# not apportion him and each bears the whole deduction. +- name: HB, a lodger's adult son is a non-dependant of the lodger and of the householder + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 60 + is_household_head: true + is_claimant_or_partner: true + lodger: + age: 45 + is_claimant_or_partner: true + rent_paid_as_lodger: 5_200 + lodger_son: + age: 21 + is_claimant_or_partner: false + employment_income: 2_600 + weekly_hours: 10 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [lodger, lodger_son] + households: + household: + members: [head, lodger, lodger_son] + rent: 9_000 + output: + housing_benefit_non_dep_deductions: [1_060.80, 1_060.80] + +- name: CTR, the claimant's family bears the non-dependant in its benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + council_tax_reduction_individual_non_dep_deduction: 300 + benunits: + benunit: + members: [parent, son] + households: + household: + members: [parent, son] + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, true] + council_tax_reduction_non_dep_deductions: 300 + +- name: CTR, no deduction for a non-dependant in the benefit unit under 18 + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 17 + is_claimant_or_partner: false + is_child_or_qualifying_young_person_for_child_benefit: false + benunits: + benunit: + members: [parent, son] + households: + household: + members: [parent, son] + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, false] + council_tax_reduction_non_dep_deductions: 0 + +# An applicant's adult son normally resides with both jointly liable people +# and is in neither's family (SI 2012/2885 reg 9(1), (2)(a)), so the deduction +# is apportioned equally between them (Sch 1 para 8(5)). +- name: CTR, jointly liable applicants share the deduction for one applicant's adult son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + council_tax_reduction_individual_non_dep_deduction: 300 + sharer: + age: 48 + is_claimant_or_partner: true + benunits: + benunit_1: + members: [head, son] + benunit_2: + members: [sharer] + liable_for_share_of_household_rent: true + households: + household: + members: [head, son, sharer] + rent: 12_000 + output: + council_tax_reduction_joint_liability_share: [0.5, 0.5] + council_tax_reduction_non_dep_deductions: [150, 150] + +# Para 8(5) still apportions a non-dependant from another family between the +# jointly liable people. +- name: CTR, jointly liable families share the deduction for another family's adult + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + sharer: + age: 48 + is_claimant_or_partner: true + friend: + age: 30 + is_claimant_or_partner: true + council_tax_reduction_individual_non_dep_deduction: 300 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [sharer] + liable_for_share_of_household_rent: true + benunit_3: + members: [friend] + households: + household: + members: [head, sharer, friend] + rent: 12_000 + output: + council_tax_reduction_non_dep_deductions: [150, 150, 0] + +# SI 2012/2885 reg 9(2)(e) excludes only the person liable to pay the +# applicant, so a lodger's adult son who pays nothing himself is the +# applicant's non-dependant. +- name: CTR, a lodger's adult son is the applicant's non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 60 + is_household_head: true + is_claimant_or_partner: true + lodger: + age: 45 + is_claimant_or_partner: true + rent_paid_as_lodger: 5_200 + lodger_son: + age: 21 + is_claimant_or_partner: false + council_tax_reduction_individual_non_dep_deduction: 300 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [lodger, lodger_son] + households: + household: + members: [head, lodger, lodger_son] + rent: 9_000 + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, false, true] + council_tax_reduction_non_dep_deductions: [300, 0] + +# Only a family that claims bears deductions; the non-dependant's own family +# does not. +- name: CTR, a non-dependant family with an adult member bears no deductions itself + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 70 + is_household_head: true + is_claimant_or_partner: true + daughter: + age: 45 + is_claimant_or_partner: true + council_tax_reduction_individual_non_dep_deduction: 200 + grandson: + age: 23 + is_claimant_or_partner: false + council_tax_reduction_individual_non_dep_deduction: 100 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [daughter, grandson] + households: + household: + members: [head, daughter, grandson] + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, true, true] + council_tax_reduction_non_dep_deductions: [300, 0] + +# With the Housing Benefit and Council Tax Reduction scales and exemptions as +# legislated (#2004): a non-dependant within the benefit unit is banded on +# their own income and exempt only on their own circumstances. + +# HB reg 74(1)(b): a non-dependant aged 18 or over not in remunerative work +# pays the lowest amount, £20.40 a week, whatever their income. +- name: HB, an adult son in the claimant's benefit unit with no income pays the lowest amount + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + benunits: + benunit: + members: [parent, son] + households: + household: + members: [parent, son] + output: + housing_benefit_non_dep_deductions: 1_060.80 + +# HB reg 74(7)(c); CTR SI 2012/2885 Sch 1 para 8(7)(c). +- name: HB and CTR, no deduction for a full-time student in the claimant's benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 70 + is_household_head: true + is_claimant_or_partner: true + son: + age: 20 + is_claimant_or_partner: false + current_education: TERTIARY + benunits: + benunit: + members: [parent, son] + households: + household: + members: [parent, son] + country: ENGLAND + local_authority: MAIDSTONE + output: + housing_benefit_non_dep_deductions: 0 + council_tax_reduction_non_dep_deductions: 0 + +# HB reg 74(8)(a) exempts a non-dependant under 25 who is on Income Support. +# The family's Income Support is the parent's award, so the 22-year-old son is +# not on it and is not exempt. +- name: HB, a parent's Income Support does not exempt the adult son in their benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 22 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + benunits: + benunit: + members: [parent, son] + income_support: 3_000 + households: + household: + members: [parent, son] + output: + housing_benefit_non_dep_deduction_exempt: [false, false] + housing_benefit_non_dep_deductions: 1_060.80 + +# HB reg 74(2)-(4): a non-dependant couple pays one deduction, the higher, on +# their joint income; their adult son is a non-dependant in his own right, on +# his own income. Father and son each earn £300 a week working full time, so +# the couple and the son each pay the £279-£365 band's £64.35 (not one £119.85 +# on a £600 family income): £128.70 a week, £6,692.40 a year. +- name: HB, a non-dependant couple and their adult son pay two deductions + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 75 + is_household_head: true + is_claimant_or_partner: true + father: + age: 55 + is_claimant_or_partner: true + employment_income: 15_600 + weekly_hours: 37.5 + current_education: NOT_IN_EDUCATION + mother: + age: 52 + is_claimant_or_partner: true + current_education: NOT_IN_EDUCATION + son: + age: 23 + is_claimant_or_partner: false + employment_income: 15_600 + weekly_hours: 37.5 + current_education: NOT_IN_EDUCATION + benunits: + head_unit: + members: [head] + family: + members: [father, mother, son] + would_claim_uc: false + households: + household: + members: [head, father, mother, son] + output: + household_benefits_individual_non_dep_deduction: [0, 3_346.20, 1_060.80, 3_346.20] + housing_benefit_non_dep_deductions: [6_692.40, 0] + +# SI 2012/2885 Sch 1 para 8(2)(c): £576.92 a week is in the £485-£605 band, +# £13.30 a week in 2026-27, £691.60 a year. +- name: CTR, England pensioner scheme charges the working son in the claimant's benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + is_household_head: true + is_claimant_or_partner: true + son: + age: 40 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + current_education: NOT_IN_EDUCATION + benunits: + benunit: + members: [pensioner, son] + households: + household: + members: [pensioner, son] + country: ENGLAND + local_authority: MAIDSTONE + output: + council_tax_reduction_individual_non_dep_deduction: [0, 691.60] + council_tax_reduction_non_dep_deductions: 691.60 + +# SI 2012/2885 Sch 1 para 8(6): the exemption belongs to each applicant ("the +# applicant or his partner"). With two jointly liable applicants, one's +# blindness does not exempt the other's claim. +- name: CTR, a blind household head does not exempt a jointly liable sharer + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + is_blind: true + sharer: + age: 48 + is_claimant_or_partner: true + friend: + age: 30 + is_claimant_or_partner: true + council_tax_reduction_individual_non_dep_deduction: 300 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [sharer] + liable_for_share_of_household_rent: true + benunit_3: + members: [friend] + households: + household: + members: [head, sharer, friend] + rent: 12_000 + country: SCOTLAND + output: + council_tax_reduction_applicant_has_non_dep_exemption: [true, false, false] + council_tax_reduction_non_dep_deductions: [0, 150, 0] + +- name: CTR, a blind sharer is exempt but the household head is not + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + sharer: + age: 48 + is_claimant_or_partner: true + is_blind: true + friend: + age: 30 + is_claimant_or_partner: true + council_tax_reduction_individual_non_dep_deduction: 300 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [sharer] + liable_for_share_of_household_rent: true + benunit_3: + members: [friend] + households: + household: + members: [head, sharer, friend] + rent: 12_000 + country: SCOTLAND + output: + council_tax_reduction_applicant_has_non_dep_exemption: [false, true, false] + council_tax_reduction_non_dep_deductions: [150, 0, 0] + +# The sharer's adult son is a non-dependant of both jointly liable people. +# The blind sharer is exempt; the head is not and bears half (para 8(5)). +- name: CTR, a blind sharer is exempt for its own adult son but the head still bears half + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + sharer: + age: 48 + is_claimant_or_partner: true + is_blind: true + sharer_son: + age: 25 + is_claimant_or_partner: false + council_tax_reduction_individual_non_dep_deduction: 300 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [sharer, sharer_son] + liable_for_share_of_household_rent: true + households: + household: + members: [head, sharer, sharer_son] + rent: 12_000 + country: SCOTLAND + output: + council_tax_reduction_non_dep_deductions: [150, 0] + +# The English working-age local schemes keep their own aggregation, which +# charges only other families' non-dependants. Widening the shared +# eligibility must not make a jointly liable sharer bear the head's own son. +- name: Local CTR (Newham), a jointly liable sharer does not bear the head's own in-unit son + period: 2025 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 20_000 + weekly_hours: 37.5 + sharer: + age: 48 + is_claimant_or_partner: true + benunits: + benunit_1: + members: [head, son] + would_claim_council_tax_reduction: true + benunit_2: + members: [sharer] + liable_for_share_of_household_rent: true + would_claim_council_tax_reduction: true + households: + household: + members: [head, son, sharer] + rent: 12_000 + region: LONDON + local_authority: NEWHAM + tenure_type: RENT_PRIVATELY + council_tax: 1_800 + output: + council_tax_reduction_joint_liability_share: [0.5, 0.5] + newham_council_tax_reduction_non_dep_deductions: [0, 0] + newham_council_tax_reduction: [630, 630] + +# SI 2012/2885 reg 9(2)(a): the applicant's own family are not its +# non-dependants, so an individual deduction set for the applicant (here as +# an input) is never charged to the applicant's own claim. +- name: CTR, an applicant is never charged a deduction for itself + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + council_tax_reduction_individual_non_dep_deduction: 300 + friend: + age: 30 + is_claimant_or_partner: true + council_tax_reduction_individual_non_dep_deduction: 200 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [friend] + households: + household: + members: [head, friend] + rent: 12_000 + output: + council_tax_reduction_claimant_benunit: [true, false] + council_tax_reduction_non_dep_deductions: [200, 0] + +# HB reg 74(5) apportions the sharer's son between the joint occupiers; reg +# 74(6) then removes the deduction from the blind sharer's claim only. The son +# earns £384.62 a week: £105.20 (reg 74(2)(d)) x 52 = £5,470.40, half each. +- name: HB, a blind sharer is exempt for its own adult son but the head still bears half + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + sharer: + age: 48 + is_claimant_or_partner: true + is_blind: true + sharer_son: + age: 25 + is_claimant_or_partner: false + employment_income: 20_000 + weekly_hours: 37.5 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [sharer, sharer_son] + liable_for_share_of_household_rent: true + households: + household: + members: [head, sharer, sharer_son] + rent: 12_000 + country: SCOTLAND + output: + household_benefits_individual_non_dep_deduction: [0, 0, 5_470.40] + housing_benefit_non_dep_deductions: [2_735.20, 0] + +# A lodger on the PIP daily living component is exempt for its own son (reg +# 74(6)); the householder, who is not exempt, still bears him in full. +- name: HB, an exempt lodger is not charged for its own adult son but the householder is + period: 2026 + absolute_error_margin: 0.01 + input: + people: + householder: + age: 60 + is_household_head: true + is_claimant_or_partner: true + lodger: + age: 45 + is_claimant_or_partner: true + pip_dl: 3_000 + rent_paid_as_lodger: 5_200 + lodger_son: + age: 25 + is_claimant_or_partner: false + employment_income: 20_000 + weekly_hours: 37.5 + benunits: + benunit_1: + members: [householder] + benunit_2: + members: [lodger, lodger_son] + households: + household: + members: [householder, lodger, lodger_son] + rent: 9_000 + country: SCOTLAND + output: + housing_benefit_non_dep_deductions: [5_470.40, 0] + +# A household head who is liable but is not her benefit unit's claimant or +# partner applies alone (council_tax_reduction_head_applies_alone). The young +# couple in her benefit unit are then outside her family and are her +# non-dependants (SI 2012/2885 reg 9(1), (2)(a)); as a couple they pay one +# deduction, the higher (Sch 1 para 8(3)). +- name: CTR, a head applying alone is charged once for the couple in her benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + grandmother: + age: 70 + is_household_head: true + is_claimant_or_partner: false + son: + age: 30 + is_claimant_or_partner: true + council_tax_reduction_individual_non_dep_deduction: 300 + partner: + age: 29 + is_claimant_or_partner: true + council_tax_reduction_individual_non_dep_deduction: 200 + benunits: + benunit: + members: [grandmother, son, partner] + households: + household: + members: [grandmother, son, partner] + rent: 12_000 + output: + council_tax_reduction_head_applies_alone: [true] + is_benefit_unit_non_dependant_for_council_tax_reduction: [false, true, true] + council_tax_reduction_non_dep_deductions: [300] + +# Sch 4 para 9(2)(f): an in-unit non-dependant counts in his own family's +# Universal Credit claim if it makes one. A pension-age head makes none, so the +# claim that counts the household's non-dependants, the jointly liable +# sharer's, counts him. +- name: UC, a non-claiming head's adult son counts in the sharer's claim + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 70 + is_household_head: true + is_claimant_or_partner: true + son: + age: 30 + is_claimant_or_partner: false + sharer: + age: 40 + is_claimant_or_partner: true + benunits: + benunit_1: + members: [head, son] + benunit_2: + members: [sharer] + liable_for_share_of_household_rent: true + households: + household: + members: [head, son, sharer] + rent: 12_000 + output: + uc_non_dependants_counted: [false, true] + uc_non_dep_deductions: [0, 1_158.60] + +# Sch 4 para 16(2)(b) exempts a non-dependant "in receipt of state pension +# credit". Pension Credit is the claimant's award, so a pensioner's adult son +# in her benefit unit is not exempt by it. +- name: UC, a pensioner non-dependant's Pension Credit does not exempt her adult son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + pensioner: + age: 75 + is_claimant_or_partner: true + pensioner_son: + age: 35 + is_claimant_or_partner: false + benunits: + benunit_1: + members: [head] + benunit_2: + members: [pensioner, pensioner_son] + households: + household: + members: [head, pensioner, pensioner_son] + rent: 12_000 + output: + uc_non_dep_receives_pension_credit: [false, true] + uc_non_dep_deduction_exempt: [false, true, false] + uc_non_dep_deductions: [1_158.60, 0] + +# The household head is the householder in whose name the accommodation is +# rented, so she is no one's non-dependant for UC (Sch 4 para 9(2)(d)) or HB +# (reg 3(2)(d)), even where her benefit unit's claimant and partner are the +# young couple. For CTR she applies alone and they are her non-dependants. +- name: UC and HB, a head who applies for CTR alone is not her benefit unit's non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + grandmother: + age: 70 + is_household_head: true + is_claimant_or_partner: false + son: + age: 30 + is_claimant_or_partner: true + employment_income: 25_000 + weekly_hours: 37.5 + partner: + age: 29 + is_claimant_or_partner: true + benunits: + benunit: + members: [grandmother, son, partner] + households: + household: + members: [grandmother, son, partner] + rent: 12_000 + output: + is_benefit_unit_non_dependant_for_universal_credit: [false, false, false] + is_benefit_unit_non_dependant_for_legacy_benefits: [false, false, false] + is_benefit_unit_non_dependant_for_council_tax_reduction: [false, true, true] + uc_non_dep_deductions: [0] + housing_benefit_non_dep_deductions: [0] + +# Sch 4 para 9(2)(f): a family that makes no Universal Credit claim treats +# no one as its non-dependant, so a non-claiming lodger's adult son counts in +# the householder's claim. "Making a claim" is the model's would-claim flag. +- name: UC, a non-claiming lodger's adult son counts in the householder's claim + period: 2026 + absolute_error_margin: 0.01 + input: + people: + householder: + age: 40 + is_household_head: true + is_claimant_or_partner: true + lodger: + age: 39 + is_claimant_or_partner: true + rent_paid_as_lodger: 5_200 + lodger_son: + age: 25 + is_claimant_or_partner: false + benunits: + benunit_1: + members: [householder] + benunit_2: + members: [lodger, lodger_son] + would_claim_uc: false + households: + household: + members: [householder, lodger, lodger_son] + rent: 9_000 + output: + uc_non_dependants_counted: [true, false] + uc_non_dep_deductions: [1_158.60, 0] + +# A family bearing a share of the rent is a joint occupier, never a +# non-dependant, even where the rent is nil (HB reg 3(2)(d)). Its own adult son +# is still a non-dependant of both joint occupiers, apportioned by their +# shares (reg 74(5)). +- name: HB, a nil-rent sharer is not a non-dependant but its adult son is shared + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + sharer: + age: 48 + is_claimant_or_partner: true + household_benefits_individual_non_dep_deduction: 2_000 + sharer_son: + age: 25 + is_claimant_or_partner: false + household_benefits_individual_non_dep_deduction: 1_000 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [sharer, sharer_son] + liable_for_share_of_household_rent: true + households: + household: + members: [head, sharer, sharer_son] + rent: 0 + output: + share_of_household_rent: [0.5, 0.5] + housing_benefit_non_dep_deductions: [500, 500] + +# Sch 4 para 9(2)(f): the head's family is flagged not to claim Universal +# Credit, so its adult son counts in the jointly liable sharer's claim. +- name: UC, a non-claiming head's adult son counts in a claiming sharer's claim + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + is_household_head: true + is_claimant_or_partner: true + employment_income: 150_000 + weekly_hours: 37.5 + son: + age: 25 + is_claimant_or_partner: false + sharer: + age: 45 + is_claimant_or_partner: true + benunits: + benunit_1: + members: [head, son] + would_claim_uc: false + benunit_2: + members: [sharer] + liable_for_share_of_household_rent: true + households: + household: + members: [head, son, sharer] + rent: 12_000 + output: + uc_non_dependants_counted: [false, true] + uc_non_dep_deductions: [0, 1_158.60] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/household_benefits_individual_non_dep_deduction.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/household_benefits_individual_non_dep_deduction.yaml index f3834f57b7..863c91d88a 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/household_benefits_individual_non_dep_deduction.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/household_benefits_individual_non_dep_deduction.yaml @@ -1,7 +1,323 @@ +# 2026-27 deductions (HB Regs 2006 reg 74(1)-(2); SI 2006/214 reg 55(1)-(2); +# up-rated by SI 2026/148 art 23(3)). In remunerative work (16 hours or more, +# reg 6(1)): under £192 a week 20.40; not less than £192 46.85; £279 64.35; +# £365 105.20; £485 119.85; £605 and above 131.45. Not in remunerative work: +# 20.40 (reg 74(1)(b)). Bands include their lower edge ("not less than"). +# Family benefits (UC, tax credits, child benefit), which count as gross +# income, are held at zero except where a case tests them. + +- name: In remunerative work with no income pays the lowest amount + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 0 + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + # reg 74(2)(a) and (1)(b) + household_benefits_individual_non_dep_deduction: 20.4 * 52 + +- name: Just below the second band + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 9983.48 + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + # 191.99 a week + household_benefits_individual_non_dep_deduction: 20.4 * 52 + +- name: Exactly 192 a week is in the band starting at 192 + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 9984 + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 46.85 * 52 + +- name: Exactly 279 a week is in the band starting at 279 + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 14508 + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 64.35 * 52 + +- name: Exactly 365 a week is in the band starting at 365 + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 18980 + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 105.2 * 52 + +- name: Exactly 485 a week is in the band starting at 485 + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 25220 + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 119.85 * 52 + +- name: Exactly 605 a week is in the band starting at 605 + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 31460 + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 131.45 * 52 + +- name: Just below the top band + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 31459.48 + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + # 604.99 a week + household_benefits_individual_non_dep_deduction: 119.85 * 52 + +- name: Not in remunerative work pays the lowest amount whatever the income + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 40000 + weekly_hours: 0 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + # reg 74(1)(b): the bands apply only in remunerative work + household_benefits_individual_non_dep_deduction: 20.4 * 52 + +- name: Just under 16 hours is not remunerative work + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 40000 + weekly_hours: 15.9 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 20.4 * 52 + +- name: 16 hours is remunerative work + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 40000 + weekly_hours: 16 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 131.45 * 52 + +- name: A loss counts as no income + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + self_employment_income: -5_000 + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 20.4 * 52 + +- name: A couple's joint income sets the band for the member in remunerative work + period: 2026 + absolute_error_margin: 0.01 + input: + people: + worker: + age: 30 + housing_benefit_non_dep_gross_income: 10_400 # 200 a week + weekly_hours: 35 + housing_benefit_individual_non_dep_deduction_eligible: true + partner: + age: 30 + housing_benefit_non_dep_gross_income: 10_400 + weekly_hours: 0 + housing_benefit_individual_non_dep_deduction_eligible: true + benunits: + couple: + members: [worker, partner] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + households: + household: + members: [worker, partner] + output: + # reg 74(4): joint income 400 a week is in the 365 band; the council would + # deduct 20.40 for the partner outside remunerative work (reg 74(1)(b)). + household_benefits_individual_non_dep_deduction: [5_470.40, 1_060.80] + +- name: A dependent child's income does not count towards the couple's + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + housing_benefit_non_dep_gross_income: 9_360 # 180 a week + weekly_hours: 35 + housing_benefit_individual_non_dep_deduction_eligible: true + child: + age: 17 + housing_benefit_non_dep_gross_income: 5_200 + benunits: + family: + members: [parent, child] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + households: + household: + members: [parent, child] + output: + household_benefits_individual_non_dep_deduction: [1_060.80, 0] + +- name: Universal Credit counts towards gross income + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 40 + housing_benefit_non_dep_gross_income: 14_040 # 270 a week + weekly_hours: 16 + universal_credit_pre_benefit_cap: 1_040 # 20 a week + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + # reg 74(2): 290 a week gross is in the 279 band (64.35). UC is not among + # the reg 74(9) disregards. + household_benefits_individual_non_dep_deduction: 64.35 * 52 + +- name: On income-related ESA, 16 hours is not remunerative work + period: 2026 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 40 + housing_benefit_non_dep_gross_income: 10_574.72 # 203.36 a week (supported permitted work) + weekly_hours: 16 + esa_income: 5_200 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + # reg 6(6): the council treats a person on income-related ESA as outside + # remunerative work and deducts the reg 74(1)(b) amount. + household_benefits_individual_non_dep_deduction: 20.40 * 52 + +- name: A dependent young person in the family is banded on their own income, not the parent's + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + housing_benefit_non_dep_gross_income: 9_360 # 180 a week + weekly_hours: 16 + housing_benefit_individual_non_dep_deduction_eligible: true + young_person: + age: 19 + current_education: UPPER_SECONDARY + age_started_or_accepted_current_education_or_training: 18 + housing_benefit_non_dep_gross_income: 5_200 # 100 a week + weekly_hours: 8 + housing_benefit_individual_non_dep_deduction_eligible: true + benunits: + family: + members: [parent, young_person] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + households: + household: + members: [parent, young_person] + output: + # reg 74(4) joins only a couple's income. The parent's 180 a week is + # under the 192 band (20.40); for the young person, under 16 hours, the council deducts 20.40 + # unless they are exempt (eligibility is an input here). + household_benefits_individual_non_dep_deduction: [1_060.80, 1_060.80] + # 2025-26 bands and deductions (HB Regs 2006 reg 74 as up-rated by SI 2025/295 # art 23(3); DWP Benefit and pension rates 2025 to 2026): under £183 a week -# 19.65; £183-265.99 45.15; £266-347.99 62.00; £348-462.99 101.35; -# £463-576.99 115.45; £577 and above 126.65. +# 19.65; £183 45.15; £266 62.00; £348 101.35; £463 115.45; £577 and above +# 126.65. Each band runs from "not less than" its lower edge (reg 74(2)). - name: The 2018 fiscal year uses the April non-dependant deduction in HB and CTR # SI 2018/281 arts 23(3)(b), 24(3)(b), and 1(2)(h): £15.25 from @@ -12,9 +328,18 @@ council_tax_reduction_individual_non_dep_deduction_eligible: true hours_worked: 832 # 16 hours a week employment_income: 5_200 # £100 a week, in both programmes' gross income + age: 30 + current_education: NOT_IN_EDUCATION + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + country: WALES output: household_benefits_individual_non_dep_deduction: 793 - council_tax_reduction_individual_non_dep_deduction: 793 + # Council Tax Reduction has its own scale: Wales's lowest amount, £4.35 a + # week from 1 April 2018 (WSI 2018/14), £4.20 before it. + council_tax_reduction_individual_non_dep_deduction: 4.35 * 52 - name: Reduced rent without LHA period: 2025 @@ -22,6 +347,10 @@ housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 832 # 16 hours a week housing_benefit_non_dep_gross_income: 5_200 # 100 weekly + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 output: household_benefits_individual_non_dep_deduction: 19.65 * 52 @@ -31,6 +360,10 @@ housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 832 # 16 hours a week housing_benefit_non_dep_gross_income: 30_056 # 578 weekly + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 output: household_benefits_individual_non_dep_deduction: 126.65 * 52 @@ -40,42 +373,116 @@ housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 832 # 16 hours a week housing_benefit_non_dep_gross_income: 18_148 # 349 weekly + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 output: household_benefits_individual_non_dep_deduction: 101.35 * 52 - name: Income just above the lower edge of the second band - # Passes whether band edges are read inclusively (reg 74(2)) or, as the - # formula still does, exclusively; see #2004 and #2007. period: 2025 input: housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 832 # 16 hours a week housing_benefit_non_dep_gross_income: 9_517 # just over 183 weekly, the lower edge of the second band + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 output: household_benefits_individual_non_dep_deduction: 45.15 * 52 -# Other investment income remains outside the gross-income base; extending -# that base is deferred to #2007. With no other income, this non-worker has -# no banding income, and the work gate leaves the scale's zero until #2007 -# models the exemptions with reg 74(1)(b)'s flat charge. - name: Other investment income does not band a non-worker period: 2025 input: housing_benefit_individual_non_dep_deduction_eligible: true other_investment_income: 28_912 # 556 weekly, no hours + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 output: - # The same as a non-dependant with no income. Banding the £556 would give - # 115.45 * 52 = 6,003.40. - household_benefits_individual_non_dep_deduction: 0 + # Not in remunerative work, so reg 74(1)(b)'s amount whatever the income: + # 19.65 * 52 = 1,021.80. Banding the £556 would give 115.45 * 52 = + # 6,003.40. + household_benefits_individual_non_dep_deduction: 19.65 * 52 -- name: Other investment income does not move a worker's band +- name: 2025-26 top band in remunerative work period: 2025 + absolute_error_margin: 0.01 input: housing_benefit_individual_non_dep_deduction_eligible: true - hours_worked: 1_950 # 37.5 hours a week - employment_income: 9_517 # just over 183 weekly - other_investment_income: 28_912 # 556 weekly + age: 30 + housing_benefit_non_dep_gross_income: 30_056 # 578 a week + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 126.65 * 52 + +- name: 2025-26 lowest amount out of remunerative work + period: 2025 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 5_200 # 100 a week + weekly_hours: 0 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 19.65 * 52 + +- name: 2025-26 second band in remunerative work + period: 2025 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 10_400 # 200 a week + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 output: - # Banded on the £183 of earnings: 45.15 * 52 = 2,347.80. Adding the £556 - # would give the top band, 126.65 * 52 = 6,585.80. household_benefits_individual_non_dep_deduction: 45.15 * 52 + +- name: 2025-26 middle band in remunerative work + period: 2025 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + housing_benefit_non_dep_gross_income: 20_800 # 400 a week + weekly_hours: 35 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + household_benefits_individual_non_dep_deduction: 101.35 * 52 + +- name: A worker's band counts their investment income + period: 2025 + absolute_error_margin: 0.01 + input: + housing_benefit_individual_non_dep_deduction_eligible: true + age: 30 + hours_worked: 1_950 # 37.5 hours a week + employment_income: 9_517 # just over 183 a week + other_investment_income: 28_912 # 556 a week + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + output: + # In remunerative work, banded on all gross income (reg 74(2)), 739.02 a + # week: the band from £577, 126.65 * 52. Banding the earnings alone would + # give 45.15 * 52 = 2,347.80. + household_benefits_individual_non_dep_deduction: 126.65 * 52 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_individual_non_dep_deduction_eligible.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_individual_non_dep_deduction_eligible.yaml index 0307c37986..cf142dd2a3 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_individual_non_dep_deduction_eligible.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_individual_non_dep_deduction_eligible.yaml @@ -1,15 +1,21 @@ - name: Eligible period: 2025 input: - benunit_is_rent_liable: false + is_non_dependant_of_household_head: true age: 18 + # Without this the age-based default puts an 18-year-old in tertiary + # education, and full-time students are exempt (reg 74(7)(c)). + current_education: NOT_IN_EDUCATION + # An 18-year-old with no income is otherwise entitled to UC without + # earned income, which exempts them (reg 74(8)(b)). + would_claim_uc: false output: housing_benefit_individual_non_dep_deduction_eligible: true - name: Age ineligible period: 2025 input: - benunit_is_rent_liable: false + is_non_dependant_of_household_head: true age: 17 output: housing_benefit_individual_non_dep_deduction_eligible: false @@ -21,3 +27,33 @@ age: 18 output: housing_benefit_individual_non_dep_deduction_eligible: false + +# HB Regs 2006 reg 3: a member of the claimant's own benefit unit who is not +# the claimant, partner or a child or young person. Aged 25 or over, so the +# reg 74(8) exemptions for younger non-dependants on benefits do not apply. +- name: Non-dependant within the benefit unit + period: 2025 + input: + is_non_dependant_of_household_head: false + is_benefit_unit_non_dependant_for_legacy_benefits: true + age: 30 + output: + housing_benefit_individual_non_dep_deduction_eligible: true + +- name: Non-dependant within the benefit unit, under 18 + period: 2025 + input: + is_non_dependant_of_household_head: false + is_benefit_unit_non_dependant_for_legacy_benefits: true + age: 17 + output: + housing_benefit_individual_non_dep_deduction_eligible: false + +- name: Exempt non-dependant is ineligible + period: 2025 + input: + is_non_dependant_of_household_head: true + age: 20 + current_education: TERTIARY + output: + housing_benefit_individual_non_dep_deduction_eligible: false diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deduction_chain.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deduction_chain.yaml new file mode 100644 index 0000000000..f3f5835c1d --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deduction_chain.yaml @@ -0,0 +1,86 @@ +# Housing Benefit with a non-dependant, end to end through eligibility, +# exemption, banding and aggregation. Means-test inputs are fixed so the +# award is the eligible rent less the non-dependant deductions. + +- name: A zero-income full-time student does not reduce the claimant's HB + # A full-time student lives with the claimant; HB Regs 2006 + # reg 74(7)(c)-(d) exempts the student from a deduction. + period: 2025 + absolute_error_margin: 0.01 + input: + people: + tenant: + age: 40 + student: + age: 20 + employment_status: STUDENT + current_education: TERTIARY + benunits: + tenant_unit: + members: [tenant] + benunit_is_rent_liable: true + housing_benefit_eligible: true + would_claim_housing_benefit: true + benunit_rent: 6_000 + housing_benefit_applicable_amount: 0 + housing_benefit_applicable_income: 0 + LHA_eligible: false + benefit_cap_reduction: 0 + student_unit: + members: [student] + benunit_is_rent_liable: false + housing_benefit_eligible: false + would_claim_housing_benefit: false + benunit_rent: 0 + housing_benefit_applicable_amount: 0 + housing_benefit_applicable_income: 0 + LHA_eligible: false + benefit_cap_reduction: 0 + households: + home: + members: [tenant, student] + output: + housing_benefit_non_dep_deduction_exempt: [false, true] + household_benefits_individual_non_dep_deduction: [0, 0] + housing_benefit_non_dep_deductions: [0, 0] + housing_benefit: [6_000, 0] + +- name: A zero-income non-student non-dependant pays the lowest deduction + # 2026 rather than 2025 so the amount is the same before and after + # PolicyEngine/policyengine-uk#1925 (which corrects the 2025-26 rates). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + tenant: + age: 40 + son: + age: 30 + current_education: NOT_IN_EDUCATION + benunits: + tenant_unit: + members: [tenant] + benunit_is_rent_liable: true + housing_benefit_eligible: true + would_claim_housing_benefit: true + benunit_rent: 6_000 + housing_benefit_applicable_amount: 0 + housing_benefit_applicable_income: 0 + LHA_eligible: false + benefit_cap_reduction: 0 + son_unit: + members: [son] + benunit_is_rent_liable: false + housing_benefit_eligible: false + would_claim_housing_benefit: false + benunit_rent: 0 + housing_benefit_applicable_amount: 0 + housing_benefit_applicable_income: 0 + LHA_eligible: false + benefit_cap_reduction: 0 + households: + home: + members: [tenant, son] + output: + # reg 74(1)(b): 20.40 a week in 2026-27. + housing_benefit_non_dep_deductions: [1_060.80, 0] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deduction_exempt.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deduction_exempt.yaml new file mode 100644 index 0000000000..7412593b5b --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deduction_exempt.yaml @@ -0,0 +1,169 @@ +# HB Regs 2006 (SI 2006/213) reg 74(7)-(8) and (10); SI 2006/214 reg 55(7)-(9). + +- name: Full-time tertiary student is exempt + period: 2026 + input: + age: 20 + would_claim_uc: false + current_education: TERTIARY + output: + housing_benefit_non_dep_deduction_exempt: true + +- name: Full-time student in non-advanced education is exempt + period: 2026 + input: + age: 18 + would_claim_uc: false + current_education: UPPER_SECONDARY + output: + housing_benefit_non_dep_deduction_exempt: true + +- name: Higher education flag alone makes a student exempt + period: 2026 + input: + age: 30 + would_claim_uc: false + current_education: NOT_IN_EDUCATION + in_HE: true + output: + housing_benefit_non_dep_deduction_exempt: true + +- name: A worker not in education is not exempt + period: 2026 + input: + age: 30 + would_claim_uc: false + current_education: NOT_IN_EDUCATION + employment_income: 20_000 + output: + housing_benefit_non_dep_deduction_exempt: false + +- name: State Pension Credit recipient is exempt at any age + period: 2026 + input: + age: 70 + would_claim_uc: false + current_education: NOT_IN_EDUCATION + pension_credit: 1_000 + output: + housing_benefit_non_dep_deduction_exempt: true + +- name: Income Support under 25 is exempt + period: 2026 + input: + age: 24 + would_claim_uc: false + current_education: NOT_IN_EDUCATION + income_support: 1_000 + output: + housing_benefit_non_dep_deduction_exempt: true + +- name: Income Support at 25 is not exempt + period: 2026 + input: + age: 25 + would_claim_uc: false + current_education: NOT_IN_EDUCATION + income_support: 1_000 + output: + housing_benefit_non_dep_deduction_exempt: false + +- name: Income-based JSA under 25 is exempt + period: 2026 + input: + age: 24 + would_claim_uc: false + current_education: NOT_IN_EDUCATION + jsa_income: 1_000 + output: + housing_benefit_non_dep_deduction_exempt: true + +- name: Income-related ESA under 25 is not exempt without an assessment-phase input + period: 2026 + input: + age: 24 + would_claim_uc: false + current_education: NOT_IN_EDUCATION + esa_income: 1_000 + output: + # reg 74(8)(a) exempts only ESA(IR) without the support component and + # outside the work-related activity group; the model has no ESA group input. + housing_benefit_non_dep_deduction_exempt: false + +- name: Universal Credit without earned income under 25 is exempt + period: 2026 + input: + age: 24 + current_education: NOT_IN_EDUCATION + universal_credit_pre_benefit_cap: 1_000 + output: + housing_benefit_non_dep_deduction_exempt: true + +- name: Universal Credit with earned income under 25 is not exempt + period: 2026 + input: + age: 24 + current_education: NOT_IN_EDUCATION + universal_credit_pre_benefit_cap: 1_000 + employment_income: 2_000 + output: + housing_benefit_non_dep_deduction_exempt: false + +- name: Universal Credit without earned income at 25 is not exempt + period: 2026 + input: + age: 25 + current_education: NOT_IN_EDUCATION + universal_credit_pre_benefit_cap: 1_000 + output: + housing_benefit_non_dep_deduction_exempt: false + +- name: Another member's Universal Credit claim doesn't exempt a non-dependant under 25 + # The family's Universal Credit is the 40-year-old's own claim + # (is_uc_claimant), so the 22-year-old partner with no earnings is not + # entitled to it (HB Regs 2006 reg 74(8)(b); SI 2012/2885 Sch 1 para + # 8(8)(c)). The 40-year-old has earnings, so the limb exempts no one. + period: 2026 + input: + people: + claimant: + age: 70 + is_household_head: true + partner_a: + age: 22 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + is_uc_claimant: false + partner_b: + age: 30 + employment_income: 9_360 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + is_uc_claimant: false + other_adult: + age: 40 + employment_income: 1_040 + weekly_hours: 2 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + is_uc_claimant: true + benunits: + claimant_unit: + members: [claimant] + pension_credit: 0 + non_dependant_unit: + members: [partner_a, partner_b, other_adult] + universal_credit_pre_benefit_cap: 5_200 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, partner_a, partner_b, other_adult] + country: ENGLAND + local_authority: MAIDSTONE + output: + housing_benefit_non_dep_deduction_exempt: [false, false, false, false] + council_tax_reduction_non_dep_deduction_exempt: [false, false, false, false] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions.yaml index 015701c24d..88b1a2db3e 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions.yaml @@ -98,12 +98,80 @@ # Only person 3 is a non-dependant of the head's family: 3. housing_benefit_non_dep_deductions: [3, 0, 0] +- name: A sharer of a nil rent is not a non-dependant + period: 2025 + absolute_error_margin: 0 + input: + people: + person_1: + age: 40 + is_household_head: true + household_benefits_individual_non_dep_deduction: 0 + person_2: + age: 40 + household_benefits_individual_non_dep_deduction: 2 + benunits: + benunit_1: + members: [person_1] + housing_benefit_non_dep_deductions_claimant_exempt: false + benunit_2: + members: [person_2] + liable_for_share_of_household_rent: true + housing_benefit_non_dep_deductions_claimant_exempt: false + households: + household: + members: [person_1, person_2] + rent: 0 + output: + # Both families are liable for the rent, so each bears half. With a nil + # rent neither has any, but the sharer is still a joint occupier, not a + # non-dependant (HB Regs 2006 reg 3(2)(d)): no deduction for either. + share_of_household_rent: [0.5, 0.5] + housing_benefit_non_dep_deductions: [0, 0] + +- name: A family with any share of the rent is a joint occupier, however small + period: 2025 + absolute_error_margin: 0.001 + input: + people: + person_1: + is_household_head: true + household_benefits_individual_non_dep_deduction: 1 + person_2: + household_benefits_individual_non_dep_deduction: 2 + person_3: + household_benefits_individual_non_dep_deduction: 3 + benunits: + benunit_1: + members: [person_1] + share_of_household_rent: 0.995 + housing_benefit_non_dep_deductions_claimant_exempt: false + benunit_2: + members: [person_2] + # A nil rent leaves the family not liable for any, though it bears a + # share of the payments. + share_of_household_rent: 0.005 + benunit_is_rent_liable: false + housing_benefit_non_dep_deductions_claimant_exempt: false + benunit_3: + members: [person_3] + share_of_household_rent: 0 + benunit_is_rent_liable: false + housing_benefit_non_dep_deductions_claimant_exempt: false + households: + household: + members: [person_1, person_2, person_3] + output: + # HB Regs 2006 reg 3(2)(d): person 2 shares the payments, which makes + # them a joint occupier, not a non-dependant. The council deducts only + # person 3's 3, split by the shares: 3 x 0.995 and 3 x 0.005. + housing_benefit_non_dep_deductions: [2.985, 0.015, 0] + # The review case on #2184: an existing claimant in council housing with an # adult child whose only income is £28,912 of other investment income and who -# does no paid work. The deduction is the same as for an adult child with no -# income: the work gate leaves the scale's zero for a non-worker with no -# banding income until #2007 models the exemptions with reg 74(1)(b)'s flat -# charge. +# does no paid work. The bands apply only in remunerative work (HB Regs 2006 +# reg 74(2)), so the council deducts the reg 74(1)(b) amount for the adult child, +# as it would for one with no income. - name: A non-dependant's investment income does not raise the Housing Benefit deduction period: 2025 absolute_error_margin: 0.01 @@ -126,5 +194,160 @@ tenure_type: RENT_FROM_COUNCIL rent: 15_000 output: - # Banding the £556 would give 115.45 * 52 = 6,003.40. + # 19.65 * 52 = 1,021.80; banding the £556 would give 115.45 * 52 = 6,003.40. + housing_benefit_non_dep_deductions: [1_021.80, 0] + +- name: One deduction for a non-dependant couple, the higher amount + period: 2026 + absolute_error_margin: 0 + input: + people: + claimant: + is_household_head: true + household_benefits_individual_non_dep_deduction: 0 + non_dep_1: + household_benefits_individual_non_dep_deduction: 1_060.80 + non_dep_2: + household_benefits_individual_non_dep_deduction: 5_470.40 + benunits: + claimant_unit: + members: [claimant] + couple: + members: [non_dep_1, non_dep_2] + households: + household: + members: [claimant, non_dep_1, non_dep_2] + output: + # HB Regs 2006 reg 74(3); the couple's own unit bears no deduction. + housing_benefit_non_dep_deductions: [5_470.40, 0] + +- name: No deductions where the claimant is exempt + period: 2026 + absolute_error_margin: 0 + input: + people: + claimant: + is_household_head: true + household_benefits_individual_non_dep_deduction: 0 + non_dep: + household_benefits_individual_non_dep_deduction: 1_060.80 + benunits: + claimant_unit: + members: [claimant] + housing_benefit_non_dep_deductions_claimant_exempt: true + non_dep_unit: + members: [non_dep] + housing_benefit_non_dep_deductions_claimant_exempt: false + households: + household: + members: [claimant, non_dep] + output: + # HB Regs 2006 reg 74(6). housing_benefit_non_dep_deductions: [0, 0] + +- name: Universal Credit counts in the income of the claimant it assesses + # The non-dependant family's Universal Credit is the adult son's own claim + # (is_uc_claimant), so it counts in his gross income, not his parents' + # (HB Regs 2006 reg 74(2), (4)). The parents' joint income is the father's + # 180 a week, under the 192 band: 20.40, one deduction for the couple + # (reg 74(3)). The son works 2 hours, so the council deducts 20.40 for him (reg 74(1)(b)). + # (20.40 + 20.40) * 52 = 2,121.60. Counting the son's 100 a week of UC in + # the parents' income would give 280 a week and 64.35. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_household_head: true + father: + age: 50 + employment_income: 9_360 # 180 a week + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + is_uc_claimant: false + mother: + age: 48 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + is_uc_claimant: false + son: + age: 27 + employment_income: 1_040 # 20 a week + weekly_hours: 2 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + is_uc_claimant: true + benunits: + claimant_unit: + members: [claimant] + non_dependant_unit: + members: [father, mother, son] + universal_credit_pre_benefit_cap: 5_200 # 100 a week + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, father, mother, son] + tenure_type: RENT_FROM_COUNCIL + rent: 10_000 + output: + household_benefits_individual_non_dep_deduction: [0, 1_060.80, 1_060.80, 1_060.80] + housing_benefit_non_dep_deductions: [2_121.60, 0] + +- name: Another adult's own income-related ESA stays out of the couple's income + # The adult son claims income-related ESA in his own right, so it is his + # income, not his parents' (HB Regs 2006 reg 74(4)). The parents' joint + # income is the father's 180 a week, under the 192 band: 20.40, one + # deduction for the couple. The son does no work, so the council deducts 20.40 for him (reg + # 74(1)(b)). (20.40 + 20.40) * 52 = 2,121.60. Counting his 100 a week of + # ESA in the parents' income would give 280 a week and 64.35. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_household_head: true + father: + age: 50 + employment_income: 9_360 # 180 a week + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + mother: + age: 48 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + son: + age: 27 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + claimant_unit: + members: [claimant] + non_dependant_unit: + members: [father, mother, son] + esa_income: 5_200 # the son's own award, 100 a week + claimant_or_partner_esa_income: 0 + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, father, mother, son] + tenure_type: RENT_FROM_COUNCIL + rent: 10_000 + output: + household_benefits_individual_non_dep_deduction: [0, 1_060.80, 1_060.80, 1_060.80] + housing_benefit_non_dep_deductions: [2_121.60, 0] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions_claimant_exempt.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions_claimant_exempt.yaml new file mode 100644 index 0000000000..ff959df9c7 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions_claimant_exempt.yaml @@ -0,0 +1,95 @@ +# HB Regs 2006 (SI 2006/213) reg 74(6); SI 2006/214 reg 55(6). + +- name: Blind claimant + period: 2026 + input: + age: 40 + is_blind: true + output: + housing_benefit_non_dep_deductions_claimant_exempt: true + +- name: Claimant on Attendance Allowance + period: 2026 + input: + age: 70 + aa_category: LOWER + output: + housing_benefit_non_dep_deductions_claimant_exempt: true + +- name: Partner on the DLA care component + period: 2026 + input: + people: + claimant: + age: 40 + partner: + age: 38 + dla_sc_category: LOWER + benunits: + benunit: + members: [claimant, partner] + households: + household: + members: [claimant, partner] + output: + housing_benefit_non_dep_deductions_claimant_exempt: true + +- name: Claimant on the PIP daily living component + period: 2026 + input: + age: 40 + pip_dl_category: STANDARD + output: + housing_benefit_non_dep_deductions_claimant_exempt: true + +- name: Partner on the PIP daily living component + period: 2026 + input: + people: + claimant: + age: 40 + partner: + age: 38 + pip_dl_category: STANDARD + benunits: + benunit: + members: [claimant, partner] + households: + household: + members: [claimant, partner] + output: + housing_benefit_non_dep_deductions_claimant_exempt: true + +- name: Claimant on Armed Forces Independence Payment + period: 2026 + input: + age: 40 + armed_forces_independence_payment: 1_000 + output: + housing_benefit_non_dep_deductions_claimant_exempt: true + +- name: A child's DLA care component does not exempt the claimant + period: 2026 + input: + people: + claimant: + age: 40 + child: + age: 10 + dla_sc_category: HIGHER + benunits: + benunit: + members: [claimant, child] + households: + household: + members: [claimant, child] + output: + housing_benefit_non_dep_deductions_claimant_exempt: false + +- name: DLA mobility only does not exempt the claimant + period: 2026 + input: + age: 40 + dla_m_category: HIGHER + output: + housing_benefit_non_dep_deductions_claimant_exempt: false diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.yaml index a890f3a21e..1c8bfa4f8d 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.yaml @@ -4,8 +4,7 @@ # Sch 1 para 3 and Sch 6 para 5. Gross income includes statutory pay and # maintenance; the listed disability payments are disregarded. # Hand arithmetic: wages 9,000 + statutory pay 520 = 9,520, just above -# 183/week. The existing national consumers use the HB scale (45.15/week -# at this income in 2025); correcting CTR rates is separate (#2007). +# 183/week, in the HB band from £183 (45.15/week in 2025). # Annual wages and statutory pay can relate to different parts of the year; # leave days themselves are not remunerative work (HB reg 6(7)). @@ -602,3 +601,41 @@ miscellaneous_income: -300 output: housing_benefit_non_dep_gross_income: 1_000 + +- name: housing_benefit_non_dep_gross_income counts other investment income + # DWP tells councils to count earnings and any other income (HB guidance + # A5.542). + period: 2025 + input: + social_security_income: 0 + employment_income: 1_000 + other_investment_income: 2_000 + output: + housing_benefit_non_dep_gross_income: 3_000 + +- name: housing_benefit_non_dep_gross_income counts a self-employment or property loss as nil + # The model has profits, not the receipts DWP HB guidance A5.551 counts + # before business expenses. As an approximation, a loss counts as nil and + # does not reduce other income. + period: 2025 + input: + social_security_income: 0 + employment_income: 5_000 + self_employment_income: -3_000 + property_income: -1_000 + output: + housing_benefit_non_dep_gross_income: 5_000 + +- name: housing_benefit_non_dep_gross_income counts ISA interest and dividends + # DWP tells councils to count earnings and other income (HB guidance + # A5.542), including savings interest (A5.570). This case supplies an + # exempt-interest total, so the model counts that total once. + period: 2025 + input: + social_security_income: 0 + employment_income: 9_000 + individual_savings_account_interest_income: 500 + tax_free_savings_income: 20_000 + individual_savings_account_dividend_income: 30_000 + output: + housing_benefit_non_dep_gross_income: 59_000 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_remunerative_work.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_remunerative_work.yaml index bb515785bf..f6bde9e1e8 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_remunerative_work.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/housing_benefit_non_dep_remunerative_work.yaml @@ -1,9 +1,10 @@ # HB Regs 2006 reg 74(1)(b), (2) and reg 6(1), (6); pension-age # HB Regs 2006 reg 55(1)(b), (2) and reg 6(1), (6). -# A non-worker or 15-hour worker with banding income pays 20.40 * 52 = -# 1,060.80 in 2026-27. One with no banding income keeps the scale's zero: -# the gate only lowers a deduction until #2007 models the exemptions in -# reg 74(6), (7), (8) and (10) and reg 55(6)-(9) with the flat charge. +# For a non-worker or 15-hour worker, the council deducts 20.40 * 52 = 1,060.80 in 2026-27, +# whatever their income. The exemptions in reg 74(7), (8) and (10) and reg +# 55(7)-(9) act through eligibility, which most cases here take as an input. +# Family awards, which a single non-dependant's gross income would count, are +# held at zero. # A 16-hour worker's wages of 9,000 alone are below 192/week; adding # IIDB or child maintenance of 12,000 gives 403.85/week, so 105.20 * 52. @@ -12,6 +13,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: ENGLAND housing_benefit_individual_non_dep_deduction_eligible: true @@ -31,6 +40,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: ENGLAND housing_benefit_individual_non_dep_deduction_eligible: true @@ -50,6 +67,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: ENGLAND housing_benefit_individual_non_dep_deduction_eligible: true @@ -69,6 +94,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: ENGLAND housing_benefit_individual_non_dep_deduction_eligible: true @@ -88,6 +121,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: ENGLAND housing_benefit_individual_non_dep_deduction_eligible: true @@ -107,6 +148,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: ENGLAND housing_benefit_individual_non_dep_deduction_eligible: true @@ -126,6 +175,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: ENGLAND housing_benefit_individual_non_dep_deduction_eligible: true @@ -145,6 +202,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: ENGLAND housing_benefit_individual_non_dep_deduction_eligible: true @@ -164,6 +229,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 70 housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 0 @@ -175,10 +248,18 @@ output: household_benefits_individual_non_dep_deduction: 1060.8 -- name: housing_benefit keeps a non-worker with no banding income at zero (nil income) +- name: housing_benefit charges the flat amount to a non-worker with nil income period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 70 housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 0 @@ -188,30 +269,47 @@ is_on_income_based_jsa: false is_on_income_related_esa: false output: - # Reg 74(1)(b) gives a non-exempt non-worker 1,060.80, but the zero is - # kept until #2007 models the exemptions alongside the flat charge. - household_benefits_individual_non_dep_deduction: 0 + # Reg 74(1)(b) gives a non-worker the flat amount whatever their income: + # 20.40 * 52. + household_benefits_individual_non_dep_deduction: 1060.8 - name: housing_benefit charges no new deduction to a non-dependant under 25 on Income Support # Reg 74(8)(a): no deduction for a non-dependant under 25 on Income Support. - # Income Support is not banding income, so the gate leaves the zero. + # The exemption makes them ineligible for a deduction. period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 22 - housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 0 social_security_income: 0 is_on_income_support: true is_on_income_based_jsa: false is_on_income_related_esa: false output: + housing_benefit_non_dep_deduction_exempt: true + housing_benefit_individual_non_dep_deduction_eligible: false household_benefits_individual_non_dep_deduction: 0 - name: housing_benefit excludes each income-related benefit recipient from remunerative work (Income Support) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 832 @@ -228,6 +326,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 832 @@ -244,6 +350,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 832 @@ -260,6 +374,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 housing_benefit_individual_non_dep_deduction_eligible: true hours_worked: 832 @@ -276,6 +398,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 gov.dwp.housing_benefit.non_dep_deduction.remunerative_work_hours: 20 age: 30 country: ENGLAND @@ -294,6 +424,14 @@ period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 gov.dwp.housing_benefit.non_dep_deduction.remunerative_work_hours: 20 age: 30 country: ENGLAND diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/non_dependant_deduction_review_cases.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/non_dependant_deduction_review_cases.yaml new file mode 100644 index 0000000000..633c7c7dfb --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/non_dep_deduction/non_dependant_deduction_review_cases.yaml @@ -0,0 +1,1578 @@ +# End-to-end cases for the non-dependant deductions of Housing Benefit (HB Regs +# 2006 reg 74) and the England pensioner Council Tax Reduction scheme (SI +# 2012/2885 Sch 1 para 8), at 2026-27 amounts. +# The claimant family is the rent-liable family with the household's oldest +# member. Family benefits are held at zero except where a case tests them. + +- name: A dependent 18-year-old's PIP does not exempt the claimant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + dependant: + age: 18 + current_education: UPPER_SECONDARY + age_started_or_accepted_current_education_or_training: 17 + pip_dl_category: STANDARD + lodger_son: + age: 40 + employment_income: 30_000 + weekly_hours: 37.5 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant, dependant] + benunit_is_rent_liable: true + son_unit: + members: [lodger_son] + benunit_is_rent_liable: false + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, dependant, lodger_son] + country: ENGLAND + local_authority: MAIDSTONE + output: + # reg 74(6) and para 8(6) protect the claimant or partner, not a young + # person in the family (reg 19; SSCBA s.142). The son's 576.92 a week is + # in the HB 485 band (119.85) and the CTR 485 band (13.30). + housing_benefit_non_dep_deductions_claimant_exempt: [false, false] + council_tax_reduction_applicant_has_non_dep_exemption: [false, false] + housing_benefit_non_dep_deductions: [6_232.20, 0] + council_tax_reduction_non_dep_deductions: [691.60, 0] + +- name: A 17-year-old claimant's PIP exempts their family + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 17 + current_education: NOT_IN_EDUCATION + pip_dl_category: STANDARD + sibling: + age: 16 + employment_income: 20_000 + weekly_hours: 37.5 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + sibling_unit: + members: [sibling] + benunit_is_rent_liable: false + households: + household: + members: [claimant, sibling] + output: + # The benefit unit's head is the claimant whatever their age. + housing_benefit_non_dep_deductions_claimant_exempt: [true, false] + +- name: A parent non-dependant is banded on their own income, not their young person's + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + parent: + age: 45 + employment_income: 9_360 # 180 a week + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + young_person: + age: 19 + current_education: UPPER_SECONDARY + age_started_or_accepted_current_education_or_training: 18 + employment_income: 5_200 # 100 a week + weekly_hours: 8 + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + parent_unit: + members: [parent, young_person] + benunit_is_rent_liable: false + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, parent, young_person] + country: ENGLAND + local_authority: MAIDSTONE + output: + # reg 74(4) and para 8(4) join a couple's income only. The parent's 180 a + # week is under both lowest bands; the young person is a full-time student + # (reg 74(7)(c); para 8(7)(c)). + housing_benefit_non_dep_deduction_exempt: [false, false, true] + housing_benefit_non_dep_deductions: [1_060.80, 0] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: A self-employed loss does not cancel wages for the Universal Credit exemption + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + non_dependant: + age: 24 + employment_income: 5_200 + self_employment_income: -5_200 + weekly_hours: 8 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [non_dependant] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 1_000 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, non_dependant] + country: ENGLAND + local_authority: MAIDSTONE + output: + # UC Regs 2013 reg 57(2) step 3: a self-employed loss is nil, so the wages + # are earned income and neither exemption applies. Under 16 hours: the + # lowest amounts. + housing_benefit_non_dep_deduction_exempt: [false, false] + council_tax_reduction_non_dep_deduction_exempt: [false, false] + housing_benefit_non_dep_deductions: [1_060.80, 0] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: Statutory maternity pay is earned income for the Universal Credit exemption + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + non_dependant: + age: 22 + statutory_maternity_pay: 8000 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [non_dependant] + benunit_is_rent_liable: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 2000 + households: + household: + members: [claimant, non_dependant] + country: ENGLAND + local_authority: MAIDSTONE + output: + # UC Regs 2013 reg 55(4)(b): SMP is employed earnings, so the award is not + # calculated on no earned income. Not in remunerative work: the lowest amounts. + housing_benefit_non_dep_deduction_exempt: [false, false] + council_tax_reduction_non_dep_deduction_exempt: [false, false] + housing_benefit_non_dep_deductions: [1_060.80, 0] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: Wages fully paid into a pension leave no earned income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + non_dependant: + age: 24 + employment_income: 6240 + employee_pension_contributions: 6240 + weekly_hours: 8 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [non_dependant] + benunit_is_rent_liable: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 1000 + households: + household: + members: [claimant, non_dependant] + country: ENGLAND + local_authority: MAIDSTONE + output: + # DWP deducts relievable pension contributions from employed earnings + # (UC Regs 2013 reg 55(5)(a)), leaving none (HB reg 74(8)(b); CTR para 8(8)(c)). + housing_benefit_non_dep_deduction_exempt: [false, true] + council_tax_reduction_non_dep_deduction_exempt: [false, true] + housing_benefit_non_dep_deductions: [0, 0] + council_tax_reduction_non_dep_deductions: [0, 0] + +- name: The minimum income floor counts as earned income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + non_dependant: + age: 24 + self_employment_income: 0 + uc_mif_applies: true + uc_minimum_income_floor: 20000 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [non_dependant] + benunit_is_rent_liable: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 1000 + households: + household: + members: [claimant, non_dependant] + country: ENGLAND + local_authority: MAIDSTONE + output: + # DWP treats a person to whom the floor applies as having earned income + # equal to it (UC Regs 2013 reg 62(2)). + housing_benefit_non_dep_deduction_exempt: [false, false] + council_tax_reduction_non_dep_deduction_exempt: [false, false] + housing_benefit_non_dep_deductions: [1_060.80, 0] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: Income-related ESA is payable to the recipient, not the working partner + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + recipient: + age: 45 + esa_income_reported: 2600 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + partner: + age: 40 + employment_income: 10920 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [recipient, partner] + benunit_is_rent_liable: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 2600 + pension_credit: 0 + would_claim_uc: false + households: + household: + members: [claimant, recipient, partner] + country: ENGLAND + local_authority: MAIDSTONE + output: + # HB Regs 2006 reg 2(3A): only the recipient is on ESA(IR), so only they are + # out of remunerative work (reg 6(6)) and exempt from CTR (para 8(8)(a)). The + # partner is banded on the couple's 260 a week (210 wages plus 50 ESA), in + # the HB 192 band (46.85) and under the CTR 279 band (5.20); one deduction + # for the couple, the higher. + housing_benefit_non_dep_deductions: [2_436.20, 0] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: Gross income counts Universal Credit after the benefit cap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + non_dependant: + age: 40 + employment_income: 52 # 1 a week, so not exempt as UC without earnings + weekly_hours: 35 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [non_dependant] + benunit_is_rent_liable: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 30000 + uc_benefit_cap: 20000 + households: + household: + members: [claimant, non_dependant] + country: ENGLAND + local_authority: MAIDSTONE + output: + # UC Regs 2013 reg 81: the cap reduces the award to 20,000; with 1 a week of + # wages, 385.62 a week, + # in the HB 365 band (105.20) and the CTR 279 band (10.60). Before the cap it + # would be 577.92 a week. + housing_benefit_non_dep_deductions: [5_470.40, 0] + council_tax_reduction_non_dep_deductions: [551.20, 0] + +- name: A young person for whom child benefit is paid is disregarded for CTR + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + parent: + age: 45 + employment_income: 10400 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + young_person: + age: 19 + current_education: NOT_IN_EDUCATION + is_in_approved_training: true + age_started_or_accepted_current_education_or_training: 16 + weekly_hours: 0 + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [parent, young_person] + benunit_is_rent_liable: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 1406.6 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + would_claim_uc: false + households: + household: + members: [claimant, parent, young_person] + country: ENGLAND + local_authority: MAIDSTONE + output: + # CTR para 8(8)(b) and LGFA 1992 Sch 1 para 3: the 19-year-old on approved + # training is disregarded, so only the parent is deducted: 227.05 a week with + # the child benefit, under 279 (5.20). HB has no such exemption: the parent + # (192 band, 46.85) plus the young person, not in remunerative work (20.40). + council_tax_reduction_non_dep_deduction_exempt: [false, false, true] + council_tax_reduction_non_dep_deductions: [270.40, 0] + housing_benefit_non_dep_deduction_exempt: [false, false, false] + housing_benefit_non_dep_deductions: [3_497.00, 0] + +# Partners are the SSCBA s.137 couple (is_claimant_or_partner), not whoever is +# not a child benefit child or qualifying young person. + +- name: A blind 16-year-old partner exempts a Welsh 19-year-old applicant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + applicant: + age: 19 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + partner: + age: 16 + current_education: UPPER_SECONDARY + age_started_or_accepted_current_education_or_training: 16 + is_blind: true + is_claimant_or_partner: true + lodger: + age: 18 + employment_income: 30_000 + weekly_hours: 37.5 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + applicant_unit: + members: [applicant, partner] + benunit_is_rent_liable: true + lodger_unit: + members: [lodger] + benunit_is_rent_liable: false + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, partner, lodger] + country: WALES + local_authority: CARDIFF + output: + # HB reg 74(6)(a) and WSI 2013/3029 Sch 6 para 5(6)(a): the claimant's + # partner is blind, so no deduction for the lodger (576.92 a week, which + # would otherwise be 119.85 HB and 16.80 CTR a week). + housing_benefit_non_dep_deductions_claimant_exempt: [true, false] + council_tax_reduction_applicant_has_non_dep_exemption: [true, false] + housing_benefit_non_dep_deductions: [0, 0] + council_tax_reduction_non_dep_deductions: [0, 0] + +- name: A non-dependant couple with a partner at school is banded on joint income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_claimant_or_partner: true + older_partner: + age: 20 + employment_income: 9_360 + weekly_hours: 37.5 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + younger_partner: + age: 18 + employment_income: 8_320 + weekly_hours: 10 + current_education: UPPER_SECONDARY + age_started_or_accepted_current_education_or_training: 16 + is_claimant_or_partner: true + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + couple_unit: + members: [older_partner, younger_partner] + benunit_is_rent_liable: false + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, older_partner, younger_partner] + country: ENGLAND + local_authority: MAIDSTONE + output: + # HB reg 74(3)-(4) and CTR Sch 1 para 8(3)-(4): one deduction for the + # couple, banded on their joint 340 a week because the older partner is in + # remunerative work: 64.35 HB (279 band) and 10.60 CTR (279 band). + housing_benefit_non_dep_deductions: [3_346.20, 0] + council_tax_reduction_non_dep_deductions: [551.20, 0] + +- name: A 17-year-old partner's PIP exempts a Merton applicant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + applicant: + age: 50 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + partner: + age: 17 + current_education: UPPER_SECONDARY + age_started_or_accepted_current_education_or_training: 16 + pip_dl_category: STANDARD + is_claimant_or_partner: true + lodger: + age: 40 + employment_income: 30_000 + weekly_hours: 37.5 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + applicant_unit: + members: [applicant, partner] + would_claim_uc: false + claims_all_entitled_benefits: true + lodger_unit: + members: [lodger] + would_claim_uc: false + households: + household: + members: [applicant, partner, lodger] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # Merton scheme para 30(6): no deduction where the applicant or partner + # gets the daily living component of PIP. + council_tax_reduction_applicant_has_non_dep_exemption: [true, false] + merton_council_tax_reduction_non_dep_deductions: [0, 0] + +# Gross and earned income, the Scottish working-age scheme, the benefit cap's +# childcare protection, and child benefit entitlement. + +- name: Statutory maternity pay counts towards a non-dependant couple's gross income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 80 + is_claimant_or_partner: true + worker: + age: 30 + employment_income: 13_520 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + partner: + age: 28 + statutory_maternity_pay: 1_560 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + couple_unit: + members: [worker, partner] + benunit_is_rent_liable: false + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, worker, partner] + country: ENGLAND + local_authority: MAIDSTONE + output: + # HB Regs 2006 reg 35(1)(i): statutory maternity pay is earnings. The + # couple's joint gross income is 260 wages plus 30 SMP, 290 a week (reg + # 74(4); CTR para 8(4)): the HB 279 band (64.35) and the CTR 279 band + # (10.60). Without the SMP, 260 would be a band lower in each. + housing_benefit_non_dep_deductions: [3_346.20, 0] + council_tax_reduction_non_dep_deductions: [551.20, 0] + +- name: A couple's income-related ESA counts towards their joint gross income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 80 + is_claimant_or_partner: true + recipient: + age: 45 + esa_income_reported: 5_200 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + partner: + age: 40 + employment_income: 9_360 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + couple_unit: + members: [recipient, partner] + benunit_is_rent_liable: false + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 5_200 + pension_credit: 0 + households: + household: + members: [claimant, recipient, partner] + country: ENGLAND + local_authority: MAIDSTONE + output: + # The partner works 16 hours and is banded on the couple's 180 wages plus + # 100 ESA(IR), 280 a week: the HB 279 band (64.35) and the CTR 279 band + # (10.60). The recipient is out of remunerative work (reg 6(6)) and exempt + # from CTR (para 8(8)(a)); one deduction for the couple, the higher. + council_tax_reduction_non_dep_deduction_exempt: [false, true, false] + housing_benefit_non_dep_deductions: [3_346.20, 0] + council_tax_reduction_non_dep_deductions: [551.20, 0] + +- name: Statutory paternity pay is earned income for the Universal Credit exemption + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 80 + is_claimant_or_partner: true + lodger: + age: 24 + statutory_paternity_pay: 300 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + lodger_unit: + members: [lodger] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 2_000 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, lodger] + country: ENGLAND + local_authority: MAIDSTONE + output: + # UC Regs 2013 reg 55(4)(c): statutory paternity pay is employed earnings, + # so the award is not calculated on no earned income and neither + # exemption applies (HB reg 74(8)(b); CTR para 8(8)(c)). The lodger does no + # remunerative work, so the council deducts the lowest amounts, 20.40 and 5.20. + housing_benefit_non_dep_deduction_exempt: [false, false] + council_tax_reduction_non_dep_deduction_exempt: [false, false] + housing_benefit_non_dep_deductions: [1_060.80, 0] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: Pension contributions are deducted from self-employed earnings in the start-up period + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 80 + is_claimant_or_partner: true + lodger: + age: 24 + self_employment_income: 6_000 + pension_contributions: 6_000 + uc_is_in_startup_period: true + weekly_hours: 10 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + lodger_unit: + members: [lodger] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 2_000 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, lodger] + country: ENGLAND + local_authority: MAIDSTONE + output: + # UC Regs 2013 reg 57(2), step 4: with no employed earnings, the 6,000 of + # relievable pension contributions comes off the 6,000 profit, leaving nil. + # The minimum income floor does not apply in the start-up period (reg + # 62(5)), so the award is on no earned income: exempt under HB reg + # 74(8)(b) (aged under 25) and CTR para 8(8)(c). + housing_benefit_non_dep_deduction_exempt: [false, true] + council_tax_reduction_non_dep_deduction_exempt: [false, true] + housing_benefit_non_dep_deductions: [0, 0] + council_tax_reduction_non_dep_deductions: [0, 0] + +- name: Scotland's working-age scheme exempts the partner of an Income Support claimant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + applicant: + age: 50 + is_claimant_or_partner: true + recipient: + age: 35 + income_support_reported: 5_000 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + partner: + age: 34 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + applicant_unit: + members: [applicant] + benunit_is_rent_liable: true + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 0 + couple_unit: + members: [recipient, partner] + benunit_is_rent_liable: false + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 5_000 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, recipient, partner] + country: SCOTLAND + local_authority: GLASGOW_CITY + output: + # SSI 2021/249 reg 90(8)(a) with reg 4(1): the partner "has a partner who + # is" on Income Support and is not on Universal Credit, so both are exempt + # from CTR. Housing Benefit has no such rule (reg 74(8)(a) is for the + # person on IS, and only under 25): both are out of remunerative work, and + # the council deducts one lowest amount for the couple, 20.40 a week. + council_tax_reduction_non_dep_deduction_exempt: [false, true, true] + council_tax_reduction_non_dep_deductions: [0, 0] + housing_benefit_non_dep_deductions: [1_060.80, 0] + +- name: Before April 2022 Scotland exempted only the Income Support claimant + period: 2021 + absolute_error_margin: 0.01 + input: + people: + applicant: + age: 50 + is_claimant_or_partner: true + recipient: + age: 35 + income_support_reported: 5_000 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + partner: + age: 34 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + applicant_unit: + members: [applicant] + benunit_is_rent_liable: true + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 0 + couple_unit: + members: [recipient, partner] + benunit_is_rent_liable: false + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 5_000 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, recipient, partner] + country: SCOTLAND + local_authority: GLASGOW_CITY + output: + # SSI 2012/303 reg 67(8)(a): only a non-dependant "who is on income + # support" is exempt. For the partner outside remunerative work, the council deducts the + # lowest 2021-22 amount, 4.30 a week. + council_tax_reduction_non_dep_deduction_exempt: [false, true, false] + council_tax_reduction_non_dep_deductions: [223.60, 0] + +- name: Scotland's pension-age scheme exempts only the Income Support claimant + period: 2026 + input: + people: + applicant: + age: 80 + is_claimant_or_partner: true + recipient: + age: 35 + income_support_reported: 5_000 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + partner: + age: 34 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + applicant_unit: + members: [applicant] + benunit_is_rent_liable: true + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 0 + couple_unit: + members: [recipient, partner] + benunit_is_rent_liable: false + would_claim_uc: false + income_support: 5_000 + pension_credit: 0 + households: + household: + members: [applicant, recipient, partner] + country: SCOTLAND + local_authority: GLASGOW_CITY + output: + # SSI 2012/319 reg 48(8)(a): a non-dependant "who is on income support". + council_tax_reduction_non_dep_deduction_exempt: [false, true, false] + +- name: The benefit cap does not reduce Universal Credit when childcare costs exceed the excess + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 80 + is_claimant_or_partner: true + guardian: + age: 20 + employment_income: 9_360 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + older_child: + age: 4 + is_claimant_or_partner: false + younger_child: + age: 2 + is_claimant_or_partner: false + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + guardian_unit: + members: [guardian, older_child, younger_child] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 22_000 + uc_childcare_element: 12_000 + child_benefit: 2_500 + uc_benefit_cap: 22_020 + child_tax_credit: 0 + working_tax_credit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, guardian, older_child, younger_child] + country: ENGLAND + local_authority: MAIDSTONE + output: + # UC Regs 2013 reg 81(1)-(2): the excess over the cap is 2,480, less than + # the 12,000 childcare costs element, so the award is not reduced. Gross + # income is 180 wages plus 22,000 UC and 2,500 child benefit, 651.15 a + # week: the top bands, 131.45 HB and 15.95 CTR. + housing_benefit_non_dep_deductions: [6_835.40, 0] + council_tax_reduction_non_dep_deductions: [829.40, 0] + +- name: A young person no one claims child benefit for is not disregarded for CTR + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_claimant_or_partner: true + parent: + age: 45 + employment_income: 10_400 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + young_person: + age: 19 + current_education: NOT_IN_EDUCATION + is_in_approved_training: true + age_started_or_accepted_current_education_or_training: 16 + weekly_hours: 0 + is_claimant_or_partner: false + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [parent, young_person] + benunit_is_rent_liable: false + would_claim_child_benefit: false + child_tax_credit: 0 + working_tax_credit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + would_claim_uc: false + households: + household: + members: [claimant, parent, young_person] + country: ENGLAND + local_authority: MAIDSTONE + output: + # LGFA 1992 Sch 1 para 3 needs another person to be "entitled to child + # benefit" for the young person, and entitlement needs a claim (SSAA 1992 + # s.1(1)). With no claim, the council deducts for both: the parent's 200 a week is + # under the CTR 279 band (5.20), and for the young person outside remunerative + # work, the council deducts the lowest amount (5.20). + council_tax_reduction_non_dep_deduction_exempt: [false, false, false] + council_tax_reduction_non_dep_deductions: [540.80, 0] + +- name: A non-dependant family's Universal Credit counts after the benefit cap + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 80 + is_claimant_or_partner: true + worker: + age: 20 + employment_income: 9_200 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + partner: + age: 38 + maternity_allowance: 6_000 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + child_1: + age: 16 + is_claimant_or_partner: false + child_2: + age: 15 + is_claimant_or_partner: false + child_3: + age: 13 + is_claimant_or_partner: false + child_4: + age: 11 + is_claimant_or_partner: false + child_5: + age: 0 + is_claimant_or_partner: false + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + family_unit: + members: [worker, partner, child_1, child_2, child_3, child_4, child_5] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 14_000 + uc_childcare_element: 0 + child_benefit: 5_129.80 + uc_benefit_cap: 22_020 + child_tax_credit: 0 + working_tax_credit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, worker, partner, child_1, child_2, child_3, child_4, child_5] + country: ENGLAND + local_authority: MAIDSTONE + output: + # The Universal Credit cap counts 14,000 UC and 5,129.80 child benefit, + # 19,129.80, under the 22,020 cap, so UC stays 14,000 (UC reg 81). Joint + # gross income is 9,200 wages + 6,000 Maternity Allowance + 14,000 UC + + # 5,129.80 child benefit = 660.19 a week: the top bands, 131.45 HB and + # 15.95 CTR. Welfare Reform Act 2012 s.96(10)(i) makes Maternity + # Allowance a welfare benefit, which the model's cap leaves out (#2784). + # Counting it gives 25,129.80, 3,109.80 over the cap, so UC of 10,890.20, + # 600.38 a week and the 485 bands: 6,232.20 HB and 691.60 CTR. + universal_credit: [0, 14_000] + housing_benefit_non_dep_deductions: [6_835.40, 0] + council_tax_reduction_non_dep_deductions: [829.40, 0] + +- name: A self-employment loss does not cancel a non-dependant's gross wages + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 80 + is_claimant_or_partner: true + lodger: + age: 30 + employment_income: 15_600 + self_employment_income: -10_400 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + lodger_unit: + members: [lodger] + benunit_is_rent_liable: false + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, lodger] + country: ENGLAND + local_authority: MAIDSTONE + output: + # HB reg 74(2) and SI 2006/214 reg 55(2): "normal gross weekly income". + # Taxable income nets the 10,400 loss against the 15,600 wages; gross + # income does not (DWP HB guidance A5.551), so the lodger is banded on + # 300 a week: the HB 279 band (64.35) and the CTR 279 band (10.60). + housing_benefit_non_dep_deductions: [3_346.20, 0] + council_tax_reduction_non_dep_deductions: [551.20, 0] + +- name: Scotland's partner rule does not cover a couple on Universal Credit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + applicant: + age: 50 + is_claimant_or_partner: true + recipient: + age: 35 + income_support_reported: 5_000 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + partner: + age: 34 + employment_income: 5_200 + weekly_hours: 10 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + applicant_unit: + members: [applicant] + benunit_is_rent_liable: true + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 0 + couple_unit: + members: [recipient, partner] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 1_000 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 5_000 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, recipient, partner] + country: SCOTLAND + local_authority: GLASGOW_CITY + output: + # SSI 2021/249 reg 4(1): a qualifying income-related benefit claimant "is + # not on universal credit". Here the couple is, so the partner rule does + # not apply. The recipient is exempt as on UC without earned income (reg + # 90(8)(c)); the partner has wages, so is charged the lowest amount (5.55, + # under 16 hours), the couple's one deduction. + council_tax_reduction_non_dep_deduction_exempt: [false, true, false] + council_tax_reduction_non_dep_deductions: [288.60, 0] + +- name: A young person's own Maternity Allowance does not cap the family's Universal Credit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 80 + is_claimant_or_partner: true + is_household_head: true + worker: + age: 20 + employment_income: 7_000 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + young_person: + age: 17 + current_education: UPPER_SECONDARY + age_started_or_accepted_current_education_or_training: 16 + maternity_allowance: 6_000 + is_claimant_or_partner: false + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + family_unit: + members: [worker, young_person] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 14_000 + uc_childcare_element: 0 + child_benefit: 5_129.80 + uc_benefit_cap: 22_020 + child_tax_credit: 0 + working_tax_credit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, worker, young_person] + country: ENGLAND + local_authority: MAIDSTONE + output: + # UC Regs 2013 reg 79(1): the cap counts the welfare benefits "to which a + # single person or couple is entitled". The young person's own 6,000 is + # not the claimant's, so the capped total is 19,129.80, under the cap, and + # UC stays 14,000. The worker is banded on 7,000 + 14,000 + 5,129.80 = + # 502.50 a week: the HB 485 band (119.85) and the CTR 485 band (13.30). + housing_benefit_non_dep_deductions: [6_232.20, 0] + council_tax_reduction_non_dep_deductions: [691.60, 0] + +# The Universal Credit exemption reads uc_individual_earned_income. + +- name: Employment expenses that cancel the wages leave no earned income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + current_education: NOT_IN_EDUCATION + non_dependant: + age: 24 + current_education: NOT_IN_EDUCATION + employment_income: 1_000 + employment_expenses: 1_000 + weekly_hours: 0 + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [non_dependant] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 5000 + uc_benefit_cap: 100_000 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, non_dependant] + country: WALES + local_authority: CARDIFF + output: + # DWP subtracts allowable employment expenses from employed earnings + # (UC Regs 2013 reg 55(3)(a)), leaving none (HB reg 74(8)(b), (11); WSI + # 2013/3029 Sch 1 para 3(8)(c), (10)). + housing_benefit_non_dep_deduction_exempt: [false, true] + council_tax_reduction_non_dep_deduction_exempt: [false, true] + housing_benefit_non_dep_deductions: [0, 0] + council_tax_reduction_non_dep_deductions: [0, 0] + +- name: A trading loss that cancels the profit leaves no earned income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + current_education: NOT_IN_EDUCATION + non_dependant: + age: 24 + current_education: NOT_IN_EDUCATION + self_employment_income: 1_000 + trading_loss: 1_000 + uc_mif_applies: false + weekly_hours: 0 + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [non_dependant] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 5000 + uc_benefit_cap: 100_000 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, non_dependant] + country: WALES + local_authority: CARDIFF + output: + # DWP adds the trades' profits and losses before applying the nil floor + # (UC Regs 2013 reg 57(2)), so the loss cancels the profit. The inputs + # turn off the minimum income floor. + housing_benefit_non_dep_deduction_exempt: [false, true] + council_tax_reduction_non_dep_deduction_exempt: [false, true] + housing_benefit_non_dep_deductions: [0, 0] + council_tax_reduction_non_dep_deductions: [0, 0] + +- name: A company's income attributed to its owner is earned income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + current_education: NOT_IN_EDUCATION + non_dependant: + age: 24 + current_education: NOT_IN_EDUCATION + stands_as_sole_owner_or_partner_of_company: true + owned_company_carries_on_trade: true + owned_company_income_share: 1_000 + weekly_hours: 0 + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [non_dependant] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 5000 + uc_benefit_cap: 100_000 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, non_dependant] + country: WALES + local_authority: CARDIFF + output: + # DWP attributes the company's income to its owner as self-employed + # earnings (UC Regs 2013 reg 77(3)(b)), so the exemption doesn't apply. Outside + # remunerative work the councils deduct the lowest amounts: HB 20.40 and + # the Welsh pensioner scale's 6.70 a week. + housing_benefit_non_dep_deduction_exempt: [false, false] + council_tax_reduction_non_dep_deduction_exempt: [false, false] + housing_benefit_non_dep_deductions: [1_060.80, 0] + council_tax_reduction_non_dep_deductions: [348.40, 0] + +- name: The minimum income floor doesn't apply where a partner's earnings meet the couple threshold + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + current_education: NOT_IN_EDUCATION + non_dependant: + age: 24 + current_education: NOT_IN_EDUCATION + weekly_hours: 0 + employment_income: 0 + uc_mif_applies: true + uc_minimum_income_floor: 10_000 + partner: + age: 30 + current_education: NOT_IN_EDUCATION + weekly_hours: 15 + employment_income: 30_000 + uc_mif_applies: false + uc_minimum_income_floor: 10_000 + benunits: + claimant_unit: + members: [claimant] + benunit_is_rent_liable: true + non_dependant_unit: + members: [non_dependant, partner] + benunit_is_rent_liable: false + universal_credit_pre_benefit_cap: 5000 + uc_benefit_cap: 100_000 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, non_dependant, partner] + country: WALES + local_authority: CARDIFF + output: + # DWP applies the minimum income floor to a member of a couple only while + # the couple's earnings fall below the couple threshold (UC Regs 2013 reg + # 62(3)). The partner's earned income exceeds it, so the non-dependant + # has none, and the exemption applies. The Welsh working-age scheme + # deducts for each member of a Universal Credit couple (Sch 6 para 5(3)): + # nil for the exempt non-dependant and 6.70 a week for the partner, who + # works under 16 hours. + housing_benefit_non_dep_deduction_exempt: [false, true, false] + council_tax_reduction_non_dep_deduction_exempt: [false, true, false] + council_tax_reduction_individual_non_dep_deduction: [0, 0, 348.40] + council_tax_reduction_non_dep_deductions: [348.40, 0] + +- name: A third adult's Universal Credit leaves a Welsh non-dependant couple one deduction + period: 2026 + absolute_error_margin: 0.01 + input: + people: + applicant: + age: 40 + is_household_head: true + is_claimant_or_partner: true + is_uc_claimant: false + current_education: NOT_IN_EDUCATION + first: + age: 35 + is_claimant_or_partner: true + is_uc_claimant: false + employment_income: 10_400 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + second: + age: 34 + is_claimant_or_partner: true + is_uc_claimant: false + employment_income: 5_200 + weekly_hours: 16 + current_education: NOT_IN_EDUCATION + third: + age: 25 + is_claimant_or_partner: false + is_uc_claimant: true + employment_income: 0 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + benunits: + applicant_unit: + members: [applicant] + would_claim_uc: false + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + non_dependant_unit: + members: [first, second, third] + universal_credit_pre_benefit_cap: 3_000 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + home: + members: [applicant, first, second, third] + country: WALES + local_authority: CARDIFF + rent: 6_000 + council_tax: 2_000 + tenure_type: RENT_PRIVATELY + savings: 0 + output: + # WSI 2013/3029 Sch 6 para 5(3): only the couple's own Universal Credit + # award takes it out of the one-deduction rule. Here the third adult + # claims alone, so the council bands the couple on 15,600 a year (300 a + # week) and makes one 13.35 deduction. It deducts nothing for the third + # adult, on Universal Credit with no earned income (para 5(8)(c)). + council_tax_reduction_individual_non_dep_deduction: [0, 694.20, 694.20, 0] + council_tax_reduction_non_dep_deductions: [694.20, 0] + +- name: A third adult's Universal Credit leaves the couple one deduction from each Welsh applicant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 70 + is_household_head: true + is_claimant_or_partner: true + is_uc_claimant: false + current_education: NOT_IN_EDUCATION + sharer: + age: 30 + is_claimant_or_partner: true + is_uc_claimant: false + current_education: NOT_IN_EDUCATION + first: + age: 35 + is_claimant_or_partner: true + is_uc_claimant: false + employment_income: 10_400 + hours_worked: 832 + current_education: NOT_IN_EDUCATION + second: + age: 34 + is_claimant_or_partner: true + is_uc_claimant: false + employment_income: 5_200 + hours_worked: 832 + current_education: NOT_IN_EDUCATION + third: + age: 25 + is_claimant_or_partner: false + is_uc_claimant: true + employment_income: 9_100 + hours_worked: 1_040 + current_education: NOT_IN_EDUCATION + benunits: + head_family: + members: [head] + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + sharer_family: + members: [sharer] + liable_for_share_of_household_rent: true + would_claim_uc: false + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + non_dependant_family: + members: [first, second, third] + would_claim_uc: false + universal_credit_pre_benefit_cap: 3_000 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 520 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + home: + members: [head, sharer, first, second, third] + country: WALES + local_authority: CARDIFF + rent: 12_000 + council_tax: 2_000 + tenure_type: RENT_PRIVATELY + savings: 0 + output: + # The pensioner head and the working-age sharer each apply under their + # own Welsh scheme and share the deductions equally (Sch 1 para 3(5); Sch + # 6 para 5(5)). The council bands the couple on 15,600 + 520 = 16,120 a + # year (310 a week) and makes one deduction under either scheme, as the + # Universal Credit is the third adult's. It bands the third adult on + # 9,100 + 3,000 (232.69 a week) and deducts for them separately. + council_tax_reduction_non_dep_deductions: [521.30, 521.30, 0] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/pension_age_severe_disability_conditions.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/pension_age_severe_disability_conditions.yaml index a6a1b2bde1..88cc95616c 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/pension_age_severe_disability_conditions.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/pension_age_severe_disability_conditions.yaml @@ -1,8 +1,8 @@ # HB(SPC) Regs 2006 Sch 3 para 6, with the non-dependant definition in # reg 3: these cases assert final awards without a Guarantee Credit passport. -# The final HB and CTR awards include the model's current income rules, -# including a known defect outside this file: no non-dependant deduction -# exemption for a claimant on Attendance Allowance (#2007). The premium and +# The final HB and CTR awards include the model's current income rules. +# A claimant on Attendance Allowance has no non-dependant deductions (HB +# (SPC) Regs 2006 reg 55(6); SI 2012/2885 Sch 1 para 8(6)); the premium and # applicable-amount assertions do not depend on it. Each HB award is the # rent less 65% of income after tax, plus the savings credit where the award # is savings credit only (HB (SPC) Regs 2006 reg 27(4)(a)), above the @@ -129,9 +129,8 @@ guarantee_credit: [0, 0] has_non_dependant_for_severe_disability_premium: [true, false] pension_age_severe_disability_premium: [0, 0] - # The resident has no banding income, so the work gate leaves the - # scale's zero; reg 55(6) and Sch 1 para 8(6) exempt this claimant's - # non-dependants in any case (the AA exemption remains for #2007). + # Reg 55(6) and Sch 1 para 8(6) exempt this claimant's non-dependants: + # the claimant receives Attendance Allowance. housing_benefit: [3_898.70, 0] council_tax_benefit: [1_279.60, 0] @@ -175,9 +174,8 @@ # Savings credit only (908.46): HB income is Pension Credit income plus # the savings credit (HB (SPC) Regs 2006 reg 27(4)(a)), 0.65 x 908.46 = # 590.50 below the figure without it. - # Neither non-working resident has banding income, so the work gate - # leaves the scale's zero for both; the AA exemption (reg 55(6), Sch 1 - # para 8(6)) remains for #2007. + # The AA exemption (reg 55(6), Sch 1 para 8(6)) leaves no deduction for + # either resident. housing_benefit: [3_308.20, 0] council_tax_benefit: [1_097.91, 0] diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction.yaml index 9a9ca3cced..b25b8b51f5 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction.yaml @@ -6,6 +6,8 @@ person: age: 22 uc_non_dep_deduction_exempt: false + # A member of another family who is not liable for rent. + is_non_dependant_of_household_head: true benunits: benunit: members: person @@ -21,6 +23,7 @@ person: age: 22 uc_non_dep_deduction_exempt: true + is_non_dependant_of_household_head: true benunits: benunit: members: person @@ -36,6 +39,8 @@ person: age: 20 uc_non_dep_deduction_exempt: false + # A member of another family who is not liable for rent. + is_non_dependant_of_household_head: true benunits: benunit: members: person diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction_eligible.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction_eligible.yaml index 11fe57ddce..99888fb21a 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction_eligible.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction_eligible.yaml @@ -4,7 +4,7 @@ input: age: 21 uc_non_dep_deduction_exempt: false - benunit_is_rent_liable: false + is_non_dependant_of_household_head: true output: uc_individual_non_dep_deduction_eligible: true @@ -14,6 +14,40 @@ input: age: 20 uc_non_dep_deduction_exempt: false - benunit_is_rent_liable: false + is_non_dependant_of_household_head: true + output: + uc_individual_non_dep_deduction_eligible: false + +- name: Exempt + period: 2020 + absolute_error_margin: 0 + input: + age: 21 + uc_non_dep_deduction_exempt: true + is_non_dependant_of_household_head: true + output: + uc_individual_non_dep_deduction_eligible: false + +# UC Regs 2013 Sch 4 para 9(1)(c): a member of the renter's own benefit unit +# who is not the claimant, partner or a qualifying young person. +- name: Non-dependant within the benefit unit + period: 2020 + absolute_error_margin: 0 + input: + age: 25 + uc_non_dep_deduction_exempt: false + is_non_dependant_of_household_head: false + is_benefit_unit_non_dependant_for_universal_credit: true + output: + uc_individual_non_dep_deduction_eligible: true + +- name: The renter is not their own non-dependant + period: 2020 + absolute_error_margin: 0 + input: + age: 25 + uc_non_dep_deduction_exempt: false + is_non_dependant_of_household_head: false + is_benefit_unit_non_dependant_for_universal_credit: false output: uc_individual_non_dep_deduction_eligible: false diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/uc_benefit_cap_welfare_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/uc_benefit_cap_welfare_benefits.yaml new file mode 100644 index 0000000000..61a1b2cfbb --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/uc_benefit_cap_welfare_benefits.yaml @@ -0,0 +1,61 @@ +# The welfare benefits the Universal Credit cap counts, with Housing Benefit +# at nil (UC Regs 2013 reg 80(2A)). + +- name: The Universal Credit cap leaves out Housing Benefit the family receives + period: 2025 + absolute_error_margin: 0.01 + input: + people: + adult: + age: 30 + benunits: + family: + members: [adult] + universal_credit_pre_benefit_cap: 15_000 + housing_benefit_pre_benefit_cap: 10_000 + child_benefit: 5_000 + child_tax_credit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + uc_benefit_cap: 14_753 + uc_childcare_element: 0 + uc_deductions: 0 + households: + household: + members: [adult] + country: ENGLAND + output: + # The Housing Benefit cap counts all three awards (HB Regs 2006 reg + # 75C(2)(b)); the Universal Credit cap counts 15,000 + 5,000 = 20,000, + # 5,247 over the cap (reg 81). + benefit_cap_welfare_benefits: 30_000 + uc_benefit_cap_welfare_benefits: 20_000 + uc_benefit_cap_reduction: 5_247 + universal_credit: 9_753 + +- name: A supplied benefit cap total includes Housing Benefit, which the Universal Credit cap takes out + period: 2025 + absolute_error_margin: 0.01 + input: + people: + adult: + age: 30 + benunits: + family: + members: [adult] + universal_credit_pre_benefit_cap: 20_000 + housing_benefit_pre_benefit_cap: 6_000 + benefit_cap_welfare_benefits: 26_000 + uc_benefit_cap: 14_753 + uc_childcare_element: 0 + uc_deductions: 0 + households: + household: + members: [adult] + country: ENGLAND + output: + # 26,000 - 6,000 = 20,000, 5,247 over the cap, so the award is 14,753. + uc_benefit_cap_welfare_benefits: 20_000 + uc_benefit_cap_reduction: 5_247 + universal_credit: 14_753 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 index 53e746bdf2..09bbd8efd9 100644 --- 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 @@ -320,6 +320,7 @@ uc_benefit_cap: 22_020 uc_childcare_element: 9_000 universal_credit_pre_benefit_cap: 15_000 + housing_benefit_pre_benefit_cap: 0 output: # Excess 7,980 < childcare 9,000. uc_benefit_cap_reduction: 0 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_family_scheme.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_family_scheme.yaml index ac49d27620..7dc97e3c1e 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_family_scheme.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_family_scheme.yaml @@ -250,13 +250,14 @@ council_tax_reduction_claimant_benunit: [true, false] # The adult child's own scheme (Maidstone's working-age one) is not # simulated, but the household's bill is: the head's pensioner claim - # covers all of it (the non-working child has no banding income, so the - # work gate leaves the scale's zero). The child's report cannot be added - # on top. + # covers all of it, less the non-dependant deduction for the adult child, + # who is not in remunerative work, not a student and has no award. The + # council deducts the lowest amount (SI 2012/2885 Sch 1 para 8(1)(b): 5.20 * 52 = + # 270.40). The child's report cannot be added on top. council_tax_reduction_scheme_supported: [true, false] council_tax_reduction_household_has_simulated_claim: true - council_tax_benefit: [1_800, 0] - council_tax_reduction: 1_800 + council_tax_benefit: [1_529.60, 0] + council_tax_reduction: 1_529.60 - name: Where no claim is simulated, every family keeps its reported reduction period: 2026 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_household_head.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_household_head.yaml index 720ebe1016..dd3a9e2b63 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_household_head.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_household_head.yaml @@ -718,9 +718,8 @@ # student shares liability with a non-student, the non-student is solely # liable (LGFA 1992 s.75(4)), so the co-tenant claims on the whole bill # and the head is the co-tenant's non-dependant. No deduction is made for - # a full-time student non-dependant (reg 90(7)(c)). That exemption - # remains for #2007; the model's national deduction is nil here because - # the head has no banding income, which the work gate leaves at zero. + # a full-time student non-dependant (reg 90(7)(c)), so the co-tenant's + # reduction covers the whole bill. council_tax_reduction_liable_person: [true, true] council_tax_reduction_claimant_benunit: [false, true] council_tax_reduction_joint_liability_share: [0.5, 1] diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction.yaml index c63c7d59df..d96e582366 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction.yaml @@ -1,31 +1,73 @@ -# Income bands apply only in remunerative work (SI 2012/2885 Sch 1 para -# 8(1)-(2)). This scoped gate retains the existing HB scale and the exclusion -# of other investment income from gross income, and leaves a non-worker with -# no banding income at the scale's zero; national CTR rates, the exemptions -# and any extension of the income base remain for #2007. +# The national Council Tax Reduction non-dependant deduction bands normal +# gross weekly income, including investment income, but only for a +# non-dependant in remunerative work (SI 2012/2885 Sch 1 para 8(1)-(2)); the council +# deducts the lowest amount for one outside remunerative work whatever their income. The +# England pensioner scheme's 2025-26 scale (SI 2025/39): under £266 a week +# 5.00; £266 10.20; £463 12.80; £577 and above 15.35. -- name: Other investment income does not band a non-worker for CTR +- name: Investment income does not band a non-worker for CTR period: 2025 + absolute_error_margin: 0.01 input: - council_tax_reduction_individual_non_dep_deduction_eligible: true - other_investment_income: 28_912 # 556 weekly, no hours + people: + claimant: + age: 70 + is_household_head: true + adult_child: + age: 30 + other_investment_income: 28_912 # 556 a week, no hours + current_education: NOT_IN_EDUCATION + benunits: + claimant_family: + members: [claimant] + adult_child_family: + members: [adult_child] + would_claim_uc: false + universal_credit_pre_benefit_cap: 0 + households: + household: + members: [claimant, adult_child] + council_tax: 2_000 + country: ENGLAND + local_authority: MAIDSTONE output: - # The same as a non-dependant with no income. Banding the £556 would give - # 115.45 * 52 = 6,003.40. - council_tax_reduction_individual_non_dep_deduction: 0 + # Not in remunerative work: the lowest amount, 5.00 * 52, as for a + # non-dependant with no income. Banding the £556 would give 12.80 * 52. + council_tax_reduction_individual_non_dep_deduction: [0, 260] -- name: Other investment income does not move a worker's CTR band +- name: A worker's CTR band counts their investment income period: 2025 + absolute_error_margin: 0.01 input: - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 1_950 # 37.5 hours a week - employment_income: 9_517 # just over 183 weekly - other_investment_income: 28_912 # 556 weekly + people: + claimant: + age: 70 + is_household_head: true + adult_child: + age: 30 + hours_worked: 1_950 # 37.5 hours a week + employment_income: 9_517 # just over 183 a week + other_investment_income: 28_912 # 556 a week + current_education: NOT_IN_EDUCATION + benunits: + claimant_family: + members: [claimant] + adult_child_family: + members: [adult_child] + would_claim_uc: false + universal_credit_pre_benefit_cap: 0 + households: + household: + members: [claimant, adult_child] + council_tax: 2_000 + country: ENGLAND + local_authority: MAIDSTONE output: - # Banded on the £183 of earnings: 45.15 * 52 = 2,347.80. - council_tax_reduction_individual_non_dep_deduction: 45.15 * 52 + # In remunerative work, banded on all gross income, 739.02 a week: the + # band from £577, 15.35 * 52. + council_tax_reduction_individual_non_dep_deduction: [0, 798.20] -- name: A pensioner's CTR is not cut for a non-dependant's investment income +- name: A pensioner's CTR is cut by the lowest amount for a non-working non-dependant with investment income period: 2025 absolute_error_margin: 0.01 input: @@ -36,11 +78,14 @@ adult_child: age: 30 other_investment_income: 28_912 # 556 a week, no hours + current_education: NOT_IN_EDUCATION benunits: claimant_family: members: [claimant] adult_child_family: members: [adult_child] + would_claim_uc: false + universal_credit_pre_benefit_cap: 0 households: household: members: [claimant, adult_child] @@ -48,6 +93,7 @@ rent: 15_000 council_tax: 2_000 country: ENGLAND + local_authority: MAIDSTONE output: - # Banding the £556 would give 115.45 * 52 = 6,003.40. - council_tax_reduction_non_dep_deductions: [0, 0] + # 5.00 * 52; banding the £556 would give 12.80 * 52 = 665.60. + council_tax_reduction_non_dep_deductions: [260, 0] diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions.yaml new file mode 100644 index 0000000000..71c342a701 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions.yaml @@ -0,0 +1,1284 @@ +# National Council Tax Reduction schemes' own non-dependant deductions: +# England pensioners SI 2012/2885 Sch 1 para 8; Wales WSI 2013/3029 Sch 1 +# para 3 and Sch 6 para 5; Scotland SSI 2012/319 reg 48 and SSI 2021/249 +# reg 90. 2026-27 weekly sums, with bands including their lower edge: +# England: under 279 5.20; 279 10.60; 485 13.30; 605 and above 15.95. +# Wales: under 279 6.70; 279 13.35; 485 16.80; 605 and above 20.05. +# Scotland: under 286 5.55; 286 10.95; 497 13.90; 614 and above 16.55. +# The council deducts the lowest amount for a non-dependant outside remunerative work (under 16 hours). +# The applicant is the benefit unit with the household's oldest member, so +# working-age applicants here are 60. + +- name: England in remunerative work with no income pays the lowest amount + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 270.40] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: England just below 279 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 14507.48 + age: 40 + current_education: NOT_IN_EDUCATION + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # 278.99 a week + council_tax_reduction_individual_non_dep_deduction: [0, 270.40] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: England exactly 279 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 14508 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 551.20] + council_tax_reduction_non_dep_deductions: [551.20, 0] + +- name: England exactly 485 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 25220 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 691.60] + council_tax_reduction_non_dep_deductions: [691.60, 0] + +- name: England just below 605 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 31459.48 + age: 40 + current_education: NOT_IN_EDUCATION + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # 604.99 a week + council_tax_reduction_individual_non_dep_deduction: [0, 691.60] + council_tax_reduction_non_dep_deductions: [691.60, 0] + +- name: England exactly 605 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 31460 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 829.40] + council_tax_reduction_non_dep_deductions: [829.40, 0] + +- name: England not in remunerative work pays the lowest amount + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 40000 + weekly_hours: 15.9 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 270.40] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: England 2025-26 top amount + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 30004 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 798.20] + council_tax_reduction_non_dep_deductions: [798.20, 0] + +- name: Scotland pensioner claimant, below 286 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 14871.48 + age: 40 + current_education: NOT_IN_EDUCATION + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + # 285.99 a week + council_tax_reduction_individual_non_dep_deduction: [0, 288.60] + council_tax_reduction_non_dep_deductions: [288.60, 0] + +- name: Scotland pensioner claimant, exactly 286 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 14872 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 569.40] + council_tax_reduction_non_dep_deductions: [569.40, 0] + +- name: Scotland working-age claimant, exactly 497 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 60 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 25844 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 722.80] + council_tax_reduction_non_dep_deductions: [722.80, 0] + +- name: Scotland working-age claimant, exactly 614 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 60 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 31928 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 860.60] + council_tax_reduction_non_dep_deductions: [860.60, 0] + +- name: Scotland 2025-26 lowest amount + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 40000 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 278.20] + council_tax_reduction_non_dep_deductions: [278.20, 0] + +- name: Wales pensioner claimant, not in remunerative work + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 40000 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 348.40] + council_tax_reduction_non_dep_deductions: [348.40, 0] + +- name: Wales pensioner claimant, exactly 279 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 14508 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 694.20] + council_tax_reduction_non_dep_deductions: [694.20, 0] + +- name: Wales working-age claimant, exactly 485 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 60 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 25220 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 873.60] + council_tax_reduction_non_dep_deductions: [873.60, 0] + +- name: Wales working-age claimant, exactly 605 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 60 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 31460 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 1_042.60] + council_tax_reduction_non_dep_deductions: [1_042.60, 0] + +- name: Wales 2025-26 lowest amount + period: 2025 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 325.00] + council_tax_reduction_non_dep_deductions: [325.00, 0] + +- name: English working-age households have no national deduction + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 60 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 36400 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # Unsupported English working-age local schemes keep reported CTB. + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +- name: A full-time student is exempt + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + age: 20 + current_education: TERTIARY + council_tax_reduction_non_dep_gross_income: 20000 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # SI 2012/2885 Sch 1 para 8(7)(c) + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +- name: A 40-year-old on Income Support is exempt + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 1000 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # para 8(8)(a) + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +- name: A 40-year-old on income-based JSA is exempt + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 1000 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +- name: A 40-year-old on income-related ESA is exempt + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 1000 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +- name: A non-dependant on State Pension Credit is exempt + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 90 + son: + age: 70 + council_tax_reduction_non_dep_gross_income: 9000 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 1000 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # The claimant is strictly older, so the son is an eligible non-dependant + # and the zero comes from the SPC exemption (para 8(8)(a)), not from the + # son also counting as the applicant. + council_tax_reduction_individual_non_dep_deduction_eligible: [false, true] + council_tax_reduction_non_dep_deduction_exempt: [false, true] + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +- name: The same non-dependant without State Pension Credit pays the lowest amount + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 90 + son: + age: 70 + council_tax_reduction_non_dep_gross_income: 9000 + weekly_hours: 0 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_non_dep_deduction_exempt: [false, false] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: Universal Credit without earned income is exempt + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 1000 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # para 8(8)(c) + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +- name: Universal Credit with earned income is not exempt + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + employment_income: 2000 + weekly_hours: 10 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 1000 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 270.40] + council_tax_reduction_non_dep_deductions: [270.40, 0] + +- name: Scotland Income Support exemption applies at working age too + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 60 + son: + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 1000 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + # SSI 2021/249 reg 90(8)(a) + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +# The UC limb starts in 2015-16 in England (SI 2014/3312) and Wales (WSI +# 2015/44). The model no longer has parameters for several other inputs in +# 2014, so the year before is not simulated here; Scotland's later start +# (2016-17) is tested in 2015-16 below. +- name: "England: Universal Credit exemption from 2015-16" + period: 2015 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 1000 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # inserted by SI 2014/3312 + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +- name: "Wales: Universal Credit exemption from 2015-16" + period: 2015 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 1000 + households: + household: + members: [claimant, son] + country: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + # added by WSI 2015/44 + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +- name: "Scotland: no Universal Credit exemption in 2015-16" + period: 2015 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 1000 + households: + household: + members: [claimant, son] + country: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction: [0, 200.20] + council_tax_reduction_non_dep_deductions: [200.20, 0] + +- name: "Scotland: Universal Credit exemption from 2016-17" + period: 2016 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + age: 40 + current_education: NOT_IN_EDUCATION + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 1000 + households: + household: + members: [claimant, son] + country: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + # inserted by SSI 2016/81 + council_tax_reduction_individual_non_dep_deduction: [0, 0.00] + council_tax_reduction_non_dep_deductions: [0.00, 0] + +- name: No deduction where the applicant is blind + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_blind: True + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 36400 + weekly_hours: 35 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # para 8(6)(a); the son's own amount is 15.95 a week, but none is deducted. + council_tax_reduction_individual_non_dep_deduction: [0, 829.40] + council_tax_reduction_non_dep_deductions: [0, 0] + +- name: "England: one deduction for a non-dependant couple, on their joint income" + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + age: 40 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 15600 + weekly_hours: 35 + partner: + age: 38 + current_education: NOT_IN_EDUCATION + council_tax_reduction_non_dep_gross_income: 0 + weekly_hours: 0 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son, partner] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [claimant, son, partner] + country: ENGLAND + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # para 8(3)-(4): joint 300 a week is in the 279 band (10.60); for the partner + # outside remunerative work the council would deduct 5.20; it deducts only the higher. + council_tax_reduction_individual_non_dep_deduction: [0, 551.20, 270.40] + council_tax_reduction_non_dep_deductions: [551.20, 0] + +- name: "Wales working age: both members of a couple on Universal Credit are deducted" + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 60 + son: + employment_income: 15600 + age: 40 + current_education: NOT_IN_EDUCATION + weekly_hours: 35 + partner: + age: 38 + current_education: NOT_IN_EDUCATION + employment_income: 1000 + weekly_hours: 0 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son, partner] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 1000 + households: + household: + members: [claimant, son, partner] + country: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + # WSI 2013/3029 Sch 6 para 5(3): one deduction per couple "other than where + # there is an award of universal credit". Joint 319.23 a week: 13.35 and 6.70. + council_tax_reduction_individual_non_dep_deduction: [0, 694.20, 348.40] + council_tax_reduction_non_dep_deductions: [1_042.60, 0] + +- name: "Wales pensioner: one deduction for a couple on Universal Credit" + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + son: + employment_income: 15600 + age: 40 + current_education: NOT_IN_EDUCATION + weekly_hours: 35 + partner: + age: 38 + current_education: NOT_IN_EDUCATION + employment_income: 1000 + weekly_hours: 0 + benunits: + claimant_unit: + members: [claimant] + son_unit: + members: [son, partner] + would_claim_uc: false + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + universal_credit_pre_benefit_cap: 1000 + households: + household: + members: [claimant, son, partner] + country: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + # Sch 1 para 3(3) has no Universal Credit exception. + council_tax_reduction_individual_non_dep_deduction: [0, 694.20, 348.40] + council_tax_reduction_non_dep_deductions: [694.20, 0] + +- name: A Pension Credit pensioner living with a working son (issue 1934) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + state_pension: 9_000 + son: + age: 40 + employment_income: 30_000 + weekly_hours: 37.5 + current_education: NOT_IN_EDUCATION + benunits: + benunit: + members: [pensioner] + claims_all_entitled_benefits: true + son_benunit: + members: [son] + would_claim_uc: false + households: + household: + members: [pensioner, son] + country: ENGLAND + local_authority: MAIDSTONE + tenure_type: OWNED_OUTRIGHT + council_tax: 2_000 + savings: 0 + output: + in_receipt_of_guarantee_credit: [true, false] + # 30,000 / 52 = 576.92 a week, in the 485 band: 13.30 a week, 691.60 a + # year. On the Housing Benefit scale it was 119.85 a week, which took the + # whole 2,000. + council_tax_reduction_non_dep_deductions: [691.60, 0] + council_tax_reduction: 1_308.40 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions_mixed_schemes.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions_mixed_schemes.yaml new file mode 100644 index 0000000000..f2f1382d1d --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions_mixed_schemes.yaml @@ -0,0 +1,249 @@ +# Jointly liable applicants under different national schemes. Each applicant's +# scheme follows its own family (SI 2012/2885 reg 3; council_tax_reduction_ +# pensioner), and a non-dependant of two jointly liable people is apportioned +# equally between them (Sch 1 para 8(5); WSI 2013/3029 Sch 1 para 3(5) and +# Sch 6 para 5(5); SSI 2021/249 reg 90(5)). 2026-27 scales: England +# pensioners 279 band 10.60; Wales 279 band 13.35; Scotland lowest 5.55, 286 +# band 10.95. + +- name: England, working-age head and pension-age sharer, deduction from the sharer's pensioner claim + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 30 + is_household_head: true + current_education: NOT_IN_EDUCATION + sharer: + age: 70 + current_education: NOT_IN_EDUCATION + non_dependant: + age: 25 + employment_income: 15_600 # 300 a week + hours_worked: 1_040 # 20 a week + current_education: NOT_IN_EDUCATION + benunits: + head_family: + members: [head] + would_claim_uc: false + sharer_family: + members: [sharer] + liable_for_share_of_household_rent: true + would_claim_uc: false + non_dependant_family: + members: [non_dependant] + would_claim_uc: false + universal_credit_pre_benefit_cap: 0 + households: + home: + members: [head, sharer, non_dependant] + country: ENGLAND + local_authority: MAIDSTONE + rent: 12_000 + tenure_type: RENT_PRIVATELY + council_tax: 2_000 + savings: 0 + output: + # The pension-age sharer's claim bears half of 10.60 * 52 = 551.20. The + # working-age head is in Maidstone's local scheme, so no national + # deduction; the person-level variable reports the head's scheme. + council_tax_reduction_non_dep_deductions: [0, 275.60, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 0, 0] + +- name: Wales, pension-age head and working-age sharer with a non-dependant UC couple + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 70 + is_household_head: true + current_education: NOT_IN_EDUCATION + first_partner: + age: 30 + employment_income: 10_400 + hours_worked: 832 # 16 a week + current_education: NOT_IN_EDUCATION + second_partner: + age: 30 + employment_income: 5_200 + hours_worked: 832 + current_education: NOT_IN_EDUCATION + sharer: + age: 30 + current_education: NOT_IN_EDUCATION + benunits: + head_family: + members: [head] + would_claim_uc: false + couple_family: + members: [first_partner, second_partner] + universal_credit_pre_benefit_cap: 3_000 + sharer_family: + members: [sharer] + liable_for_share_of_household_rent: true + would_claim_uc: false + households: + home: + members: [head, first_partner, second_partner, sharer] + country: WALES + rent: 12_000 + tenure_type: RENT_PRIVATELY + council_tax: 2_000 + savings: 0 + output: + # The couple's joint 357.69 a week is in the 279 band (13.35 * 52 = + # 694.20). The pension-age head's claim takes one deduction for the couple + # (Sch 1 para 3(3)); the working-age sharer's takes one for each member + # of a couple with a UC award (Sch 6 para 5(3)). Each bears half. + council_tax_reduction_non_dep_deductions: [347.10, 0, 694.20] + +- name: Wales, working-age head and pension-age sharer with a non-dependant UC couple + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 30 + is_household_head: true + current_education: NOT_IN_EDUCATION + first_partner: + age: 30 + employment_income: 10_400 + hours_worked: 832 + current_education: NOT_IN_EDUCATION + second_partner: + age: 30 + employment_income: 5_200 + hours_worked: 832 + current_education: NOT_IN_EDUCATION + sharer: + age: 70 + current_education: NOT_IN_EDUCATION + benunits: + head_family: + members: [head] + would_claim_uc: false + couple_family: + members: [first_partner, second_partner] + universal_credit_pre_benefit_cap: 3_000 + sharer_family: + members: [sharer] + liable_for_share_of_household_rent: true + would_claim_uc: false + households: + home: + members: [head, first_partner, second_partner, sharer] + country: WALES + rent: 12_000 + tenure_type: RENT_PRIVATELY + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_non_dep_deductions: [694.20, 0, 347.10] + +- name: Scotland, working-age head and pension-age sharer, the partner rule only for the working-age claim + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 40 + is_household_head: true + current_education: NOT_IN_EDUCATION + sharer: + age: 70 + current_education: NOT_IN_EDUCATION + recipient: + age: 35 + esa_income_reported: 5_200 + current_education: NOT_IN_EDUCATION + worker: + age: 35 + employment_income: 15_600 + hours_worked: 1_560 # 30 a week + current_education: NOT_IN_EDUCATION + benunits: + head_family: + members: [head] + would_claim_uc: false + sharer_family: + members: [sharer] + liable_for_share_of_household_rent: true + would_claim_uc: false + couple_family: + members: [recipient, worker] + esa_income: 5_200 + would_claim_uc: false + universal_credit_pre_benefit_cap: 0 + households: + home: + members: [head, sharer, recipient, worker] + country: SCOTLAND + rent: 12_000 + tenure_type: RENT_PRIVATELY + council_tax: 2_000 + savings: 0 + output: + # Working-age head (SSI 2021/249 reg 90(8)(a), reg 4(1)): the worker is + # the partner of an income-related ESA claimant not on UC, so no + # deduction. Pension-age sharer (SSI 2012/319 reg 48): only the recipient + # is exempt; the worker is banded on the couple's 400 a week, 10.95 * 52 + # = 569.40, half each. The person-level flags report the head's scheme, + # so the worker is exempt there. The 70-year-old sharer is on Pension + # Credit, so their own flag is true too; it matters only for a + # non-dependant. + council_tax_reduction_non_dep_deductions: [0, 284.70, 0] + council_tax_reduction_individual_non_dep_deduction_eligible: [false, false, true, true] + council_tax_reduction_non_dep_deduction_exempt: [false, true, true, true] + +- name: Scotland, pension-age head and working-age sharer, the partner rule only for the working-age claim + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 70 + is_household_head: true + current_education: NOT_IN_EDUCATION + sharer: + age: 40 + current_education: NOT_IN_EDUCATION + recipient: + age: 35 + esa_income_reported: 5_200 + current_education: NOT_IN_EDUCATION + worker: + age: 35 + employment_income: 15_600 + hours_worked: 1_560 + current_education: NOT_IN_EDUCATION + benunits: + head_family: + members: [head] + would_claim_uc: false + sharer_family: + members: [sharer] + liable_for_share_of_household_rent: true + would_claim_uc: false + couple_family: + members: [recipient, worker] + esa_income: 5_200 + would_claim_uc: false + universal_credit_pre_benefit_cap: 0 + households: + home: + members: [head, sharer, recipient, worker] + country: SCOTLAND + rent: 12_000 + tenure_type: RENT_PRIVATELY + council_tax: 2_000 + savings: 0 + output: + # Under the pension-age head's scheme, only the ESA recipient is exempt; + # the 70-year-old head's own flag is true because they are on Pension + # Credit. + council_tax_reduction_non_dep_deductions: [284.70, 0, 0] + council_tax_reduction_individual_non_dep_deduction_eligible: [false, false, true, true] + council_tax_reduction_non_dep_deduction_exempt: [true, false, true, false] diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.yaml index ea70a23fdd..064cdf59f2 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.yaml @@ -4,8 +4,10 @@ # Sch 1 para 3 and Sch 6 para 5. Gross income includes statutory pay and # maintenance; the listed disability payments are disregarded. # Hand arithmetic: wages 9,000 + statutory pay 520 = 9,520, just above -# 183/week. The existing national consumers use the HB scale (45.15/week -# at this income in 2025); correcting CTR rates is separate (#2007). +# 183/week. Each national scheme bands it on its own 2025-26 scale, below +# the first band's lower edge, so the council deducts the scheme's lowest +# amount for the non-dependant: England 5.00 (SI 2025/39), Scotland 5.35 (SSI 2025/24) and Wales +# 6.25 (WSI 2025/59) a week. # Annual wages and statutory pay can relate to different parts of the year; # leave days themselves are not remunerative work (HB reg 6(7)). @@ -50,9 +52,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 260] + council_tax_reduction_non_dep_deductions: [260, 0] + simulated_council_tax_reduction_benunit: [3_740, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_maternity_pay with ENGLAND claimant aged 70 period: 2025 @@ -95,9 +97,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 260] + council_tax_reduction_non_dep_deductions: [260, 0] + simulated_council_tax_reduction_benunit: [3_740, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_paternity_pay with ENGLAND claimant aged 70 period: 2025 @@ -140,9 +142,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 260] + council_tax_reduction_non_dep_deductions: [260, 0] + simulated_council_tax_reduction_benunit: [3_740, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_sick_pay with SCOTLAND claimant aged 40 period: 2025 @@ -185,9 +187,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 278.20] + council_tax_reduction_non_dep_deductions: [278.20, 0] + simulated_council_tax_reduction_benunit: [3_721.80, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_maternity_pay with SCOTLAND claimant aged 40 period: 2025 @@ -230,9 +232,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 278.20] + council_tax_reduction_non_dep_deductions: [278.20, 0] + simulated_council_tax_reduction_benunit: [3_721.80, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_paternity_pay with SCOTLAND claimant aged 40 period: 2025 @@ -275,9 +277,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 278.20] + council_tax_reduction_non_dep_deductions: [278.20, 0] + simulated_council_tax_reduction_benunit: [3_721.80, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_sick_pay with SCOTLAND claimant aged 70 period: 2025 @@ -320,9 +322,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 278.20] + council_tax_reduction_non_dep_deductions: [278.20, 0] + simulated_council_tax_reduction_benunit: [3_721.80, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_maternity_pay with SCOTLAND claimant aged 70 period: 2025 @@ -365,9 +367,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 278.20] + council_tax_reduction_non_dep_deductions: [278.20, 0] + simulated_council_tax_reduction_benunit: [3_721.80, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_paternity_pay with SCOTLAND claimant aged 70 period: 2025 @@ -410,9 +412,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 278.20] + council_tax_reduction_non_dep_deductions: [278.20, 0] + simulated_council_tax_reduction_benunit: [3_721.80, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_sick_pay with WALES claimant aged 40 period: 2025 @@ -455,9 +457,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 325] + council_tax_reduction_non_dep_deductions: [325, 0] + simulated_council_tax_reduction_benunit: [3_675, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_maternity_pay with WALES claimant aged 40 period: 2025 @@ -500,9 +502,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 325] + council_tax_reduction_non_dep_deductions: [325, 0] + simulated_council_tax_reduction_benunit: [3_675, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_paternity_pay with WALES claimant aged 40 period: 2025 @@ -545,9 +547,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 325] + council_tax_reduction_non_dep_deductions: [325, 0] + simulated_council_tax_reduction_benunit: [3_675, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_sick_pay with WALES claimant aged 70 period: 2025 @@ -590,9 +592,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 325] + council_tax_reduction_non_dep_deductions: [325, 0] + simulated_council_tax_reduction_benunit: [3_675, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_maternity_pay with WALES claimant aged 70 period: 2025 @@ -635,9 +637,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 325] + council_tax_reduction_non_dep_deductions: [325, 0] + simulated_council_tax_reduction_benunit: [3_675, 0] - name: council_tax_reduction_non_dep_gross_income includes statutory_paternity_pay with WALES claimant aged 70 period: 2025 @@ -680,9 +682,9 @@ council_tax_reduction_maximum_eligible_liability: 4_000 output: council_tax_reduction_non_dep_gross_income: [0, 9_520] - council_tax_reduction_individual_non_dep_deduction: [0, 2_347.80] - council_tax_reduction_non_dep_deductions: [2_347.80, 0] - simulated_council_tax_reduction_benunit: [1_652.20, 0] + council_tax_reduction_individual_non_dep_deduction: [0, 325] + council_tax_reduction_non_dep_deductions: [325, 0] + simulated_council_tax_reduction_benunit: [3_675, 0] - name: council_tax_reduction_non_dep_gross_income counts tax-exempt maintenance and periodic benefits period: 2025 @@ -719,3 +721,41 @@ miscellaneous_income: -300 output: council_tax_reduction_non_dep_gross_income: 1_000 + +- name: council_tax_reduction_non_dep_gross_income counts other investment income + # DWP tells councils to count earnings and any other income (HB guidance + # A5.542). + period: 2025 + input: + social_security_income: 0 + employment_income: 1_000 + other_investment_income: 2_000 + output: + council_tax_reduction_non_dep_gross_income: 3_000 + +- name: council_tax_reduction_non_dep_gross_income counts a self-employment or property loss as nil + # The model has profits, not the receipts DWP HB guidance A5.551 counts + # before business expenses. As an approximation, a loss counts as nil and + # does not reduce other income. + period: 2025 + input: + social_security_income: 0 + employment_income: 5_000 + self_employment_income: -3_000 + property_income: -1_000 + output: + council_tax_reduction_non_dep_gross_income: 5_000 + +- name: council_tax_reduction_non_dep_gross_income counts ISA interest and dividends + # DWP tells councils to count earnings and other income (HB guidance + # A5.542), including savings interest (A5.570). This case supplies an + # exempt-interest total, so the model counts that total once. + period: 2025 + input: + social_security_income: 0 + employment_income: 9_000 + individual_savings_account_interest_income: 500 + tax_free_savings_income: 20_000 + individual_savings_account_dividend_income: 30_000 + output: + council_tax_reduction_non_dep_gross_income: 59_000 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_remunerative_work.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_remunerative_work.yaml index 4c9caa26cc..c3dd3a9753 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_remunerative_work.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_remunerative_work.yaml @@ -1,171 +1,348 @@ # England SI 2012/2885 Sch 1 para 8 and reg 10; Scotland SSI 2021/249 # reg 90 and reg 10, SSI 2012/319 reg 48 and reg 6; Wales WSI 2013/3029 # Sch 1 para 3, Sch 6 para 5 and reg 10: income banding requires remunerative -# work. This scoped fix retains the consumer's inherited HB scale: the -# 2026-27 flat amount is 20.40/week and 403.85/week of income gives 105.20. -# Actual national CTR flat amounts are England 5.20, Scotland 5.55, Wales -# 6.70; their scales remain for #2007. These are work-gate regressions. A -# non-worker with no banding income keeps the scale's zero: the gate only -# lowers a deduction until #2007 models the exemptions in paragraphs (6) to -# (8) with the flat charge. +# work. Under each scheme's 2026-27 scale, the council deducts the lowest amount +# for a non-dependant outside remunerative work (England 5.20, SI 2026/27; Scotland 5.55, SSI +# 2026/56; Wales 6.70, WSI 2026/13), and 403.85/week of income in work falls +# in the band from £279 in England (10.60) and Wales (13.35) and from £286 in +# Scotland (10.95). The cases hold the family's awards, which a single +# non-dependant's gross income would count, at zero. English cases give the household a +# pension-age applicant, since England's national scheme covers pension-age +# applicants only, and the model works out each family's scheme. - name: ENGLAND council_tax_reduction bands IIDB only from 16 weekly hours (0 weekly hours) period: 2026 absolute_error_margin: 0.01 input: - age: 30 - country: ENGLAND - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 0 - employment_income: 0 - iidb: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 0 + employment_income: 0 + iidb: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - weekly_hours: 0 - council_tax_reduction_non_dep_gross_income: 12000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_pensioner: [true, false] + weekly_hours: [0, 0] + council_tax_reduction_non_dep_gross_income: [0, 12000] + council_tax_reduction_individual_non_dep_deduction: [0, 270.40] - name: ENGLAND council_tax_reduction bands IIDB only from 16 weekly hours (15 weekly hours) period: 2026 absolute_error_margin: 0.01 input: - age: 30 - country: ENGLAND - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 780 - employment_income: 9000 - iidb: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 780 + employment_income: 9000 + iidb: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - weekly_hours: 15 - council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_pensioner: [true, false] + weekly_hours: [0, 15] + council_tax_reduction_non_dep_gross_income: [0, 21000] + council_tax_reduction_individual_non_dep_deduction: [0, 270.40] - name: ENGLAND council_tax_reduction bands IIDB only from 16 weekly hours (16 weekly hours) period: 2026 absolute_error_margin: 0.01 input: - age: 30 - country: ENGLAND - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 832 - employment_income: 9000 - iidb: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 832 + employment_income: 9000 + iidb: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - weekly_hours: 16 - council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_pensioner: [true, false] + weekly_hours: [0, 16] + council_tax_reduction_non_dep_gross_income: [0, 21000] + council_tax_reduction_individual_non_dep_deduction: [0, 551.20] - name: ENGLAND council_tax_reduction bands IIDB only from 16 weekly hours (37.5 weekly hours) period: 2026 absolute_error_margin: 0.01 input: - age: 30 - country: ENGLAND - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 1950 - employment_income: 9000 - iidb: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 1950 + employment_income: 9000 + iidb: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - weekly_hours: 37.5 - council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_pensioner: [true, false] + weekly_hours: [0, 37.5] + council_tax_reduction_non_dep_gross_income: [0, 21000] + council_tax_reduction_individual_non_dep_deduction: [0, 551.20] - name: ENGLAND council_tax_reduction bands child maintenance only from 16 weekly hours (0 weekly hours) period: 2026 absolute_error_margin: 0.01 input: - age: 30 - country: ENGLAND - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 0 - employment_income: 0 - maintenance_income: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 0 + employment_income: 0 + maintenance_income: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - weekly_hours: 0 - council_tax_reduction_non_dep_gross_income: 12000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_pensioner: [true, false] + weekly_hours: [0, 0] + council_tax_reduction_non_dep_gross_income: [0, 12000] + council_tax_reduction_individual_non_dep_deduction: [0, 270.40] - name: ENGLAND council_tax_reduction bands child maintenance only from 16 weekly hours (15 weekly hours) period: 2026 absolute_error_margin: 0.01 input: - age: 30 - country: ENGLAND - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 780 - employment_income: 9000 - maintenance_income: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 780 + employment_income: 9000 + maintenance_income: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - weekly_hours: 15 - council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_pensioner: [true, false] + weekly_hours: [0, 15] + council_tax_reduction_non_dep_gross_income: [0, 21000] + council_tax_reduction_individual_non_dep_deduction: [0, 270.40] - name: ENGLAND council_tax_reduction bands child maintenance only from 16 weekly hours (16 weekly hours) period: 2026 absolute_error_margin: 0.01 input: - age: 30 - country: ENGLAND - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 832 - employment_income: 9000 - maintenance_income: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 832 + employment_income: 9000 + maintenance_income: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - weekly_hours: 16 - council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_pensioner: [true, false] + weekly_hours: [0, 16] + council_tax_reduction_non_dep_gross_income: [0, 21000] + council_tax_reduction_individual_non_dep_deduction: [0, 551.20] - name: ENGLAND council_tax_reduction bands child maintenance only from 16 weekly hours (37.5 weekly hours) period: 2026 absolute_error_margin: 0.01 input: - age: 30 - country: ENGLAND - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 1950 - employment_income: 9000 - maintenance_income: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 1950 + employment_income: 9000 + maintenance_income: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - weekly_hours: 37.5 - council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_pensioner: [true, false] + weekly_hours: [0, 37.5] + council_tax_reduction_non_dep_gross_income: [0, 21000] + council_tax_reduction_individual_non_dep_deduction: [0, 551.20] - name: SCOTLAND council_tax_reduction bands IIDB only from 16 weekly hours (0 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: SCOTLAND council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -179,12 +356,20 @@ output: weekly_hours: 0 council_tax_reduction_non_dep_gross_income: 12000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 288.60 - name: SCOTLAND council_tax_reduction bands IIDB only from 16 weekly hours (15 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: SCOTLAND council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -198,12 +383,20 @@ output: weekly_hours: 15 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 288.60 - name: SCOTLAND council_tax_reduction bands IIDB only from 16 weekly hours (16 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: SCOTLAND council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -217,12 +410,20 @@ output: weekly_hours: 16 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_individual_non_dep_deduction: 569.40 - name: SCOTLAND council_tax_reduction bands IIDB only from 16 weekly hours (37.5 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: SCOTLAND council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -236,12 +437,20 @@ output: weekly_hours: 37.5 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_individual_non_dep_deduction: 569.40 - name: SCOTLAND council_tax_reduction bands child maintenance only from 16 weekly hours (0 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: SCOTLAND council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -255,12 +464,20 @@ output: weekly_hours: 0 council_tax_reduction_non_dep_gross_income: 12000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 288.60 - name: SCOTLAND council_tax_reduction bands child maintenance only from 16 weekly hours (15 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: SCOTLAND council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -274,12 +491,20 @@ output: weekly_hours: 15 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 288.60 - name: SCOTLAND council_tax_reduction bands child maintenance only from 16 weekly hours (16 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: SCOTLAND council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -293,12 +518,20 @@ output: weekly_hours: 16 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_individual_non_dep_deduction: 569.40 - name: SCOTLAND council_tax_reduction bands child maintenance only from 16 weekly hours (37.5 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: SCOTLAND council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -312,12 +545,20 @@ output: weekly_hours: 37.5 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_individual_non_dep_deduction: 569.40 - name: WALES council_tax_reduction bands IIDB only from 16 weekly hours (0 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: WALES council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -331,12 +572,20 @@ output: weekly_hours: 0 council_tax_reduction_non_dep_gross_income: 12000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 348.40 - name: WALES council_tax_reduction bands IIDB only from 16 weekly hours (15 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: WALES council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -350,12 +599,20 @@ output: weekly_hours: 15 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 348.40 - name: WALES council_tax_reduction bands IIDB only from 16 weekly hours (16 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: WALES council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -369,12 +626,20 @@ output: weekly_hours: 16 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_individual_non_dep_deduction: 694.20 - name: WALES council_tax_reduction bands IIDB only from 16 weekly hours (37.5 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: WALES council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -388,12 +653,20 @@ output: weekly_hours: 37.5 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_individual_non_dep_deduction: 694.20 - name: WALES council_tax_reduction bands child maintenance only from 16 weekly hours (0 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: WALES council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -407,12 +680,20 @@ output: weekly_hours: 0 council_tax_reduction_non_dep_gross_income: 12000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 348.40 - name: WALES council_tax_reduction bands child maintenance only from 16 weekly hours (15 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: WALES council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -426,12 +707,20 @@ output: weekly_hours: 15 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 348.40 - name: WALES council_tax_reduction bands child maintenance only from 16 weekly hours (16 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: WALES council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -445,12 +734,20 @@ output: weekly_hours: 16 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_individual_non_dep_deduction: 694.20 - name: WALES council_tax_reduction bands child maintenance only from 16 weekly hours (37.5 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 age: 30 country: WALES council_tax_reduction_individual_non_dep_deduction_eligible: true @@ -464,12 +761,21 @@ output: weekly_hours: 37.5 council_tax_reduction_non_dep_gross_income: 21000 - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_individual_non_dep_deduction: 694.20 - name: council_tax_reduction charges the flat amount on a non-worker's pension or nil income (pension income) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + council_tax_reduction_pensioner: true age: 70 council_tax_reduction_individual_non_dep_deduction_eligible: true hours_worked: 0 @@ -479,12 +785,21 @@ is_on_income_based_jsa: false is_on_income_related_esa: false output: - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 270.40 -- name: council_tax_reduction keeps a non-worker with no banding income at zero (nil income) +- name: council_tax_reduction charges the flat amount to a non-worker with nil income period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + council_tax_reduction_pensioner: true age: 70 council_tax_reduction_individual_non_dep_deduction_eligible: true hours_worked: 0 @@ -494,150 +809,291 @@ is_on_income_based_jsa: false is_on_income_related_esa: false output: - # Paragraph (1)(b) gives a non-exempt non-worker the flat amount, but the - # zero is kept until #2007 models the exemptions alongside the flat charge. - council_tax_reduction_individual_non_dep_deduction: 0 + # Paragraph (1)(b) gives a non-worker the flat amount whatever their + # income, here 5.20 * 52; Pension Credit, which would exempt them, is held + # at zero. + council_tax_reduction_individual_non_dep_deduction: 270.40 - name: council_tax_reduction charges no new deduction to a non-dependant on Universal Credit without earnings # SI 2012/2885 Sch 1 para 8(8)(c): no deduction for a non-dependant - # entitled to Universal Credit with no earned income. Universal Credit is - # not banding income, so the gate leaves the zero. + # entitled to Universal Credit with no earned income. The applicant is of + # pension age, so England's national scheme applies. period: 2026 absolute_error_margin: 0.01 input: people: + applicant: + age: 70 + social_security_income: 0 non_dependant: age: 30 - council_tax_reduction_individual_non_dep_deduction_eligible: true hours_worked: 0 social_security_income: 0 is_on_income_support: false is_on_income_based_jsa: false is_on_income_related_esa: false benunits: + applicant_unit: + members: [applicant] non_dependant_unit: members: [non_dependant] - universal_credit: 6_000 + universal_credit_pre_benefit_cap: 6_000 households: household: - members: [non_dependant] + members: [applicant, non_dependant] country: ENGLAND output: - council_tax_reduction_individual_non_dep_deduction: [0] + council_tax_reduction_pensioner: [true, false] + council_tax_reduction_individual_non_dep_deduction: [0, 0] - name: council_tax_reduction excludes each income-related benefit recipient from remunerative work (Income Support) - # The recipient is not in remunerative work, so the flat amount applies - # rather than a band. SI 2012/2885 Sch 1 para 8(8)(a) exempts this - # non-dependant altogether; that exemption remains for #2007. + # The recipient is not in remunerative work (SI 2012/2885 reg 10(6)), and + # Sch 1 para 8(8)(a) exempts them altogether: no deduction. period: 2026 absolute_error_margin: 0.01 input: - age: 30 - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 832 - employment_income: 9000 - iidb: 12000 - social_security_income: 0 - is_on_income_support: true - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 832 + employment_income: 9000 + iidb: 12000 + social_security_income: 0 + is_on_income_support: true + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_pensioner: [true, false] + council_tax_reduction_individual_non_dep_deduction: [0, 0] - name: council_tax_reduction excludes each income-related benefit recipient from remunerative work (income-based JSA) - # The recipient is not in remunerative work, so the flat amount applies - # rather than a band. SI 2012/2885 Sch 1 para 8(8)(a) exempts this - # non-dependant altogether; that exemption remains for #2007. + # The recipient is not in remunerative work (SI 2012/2885 reg 10(6)), and + # Sch 1 para 8(8)(a) exempts them altogether: no deduction. period: 2026 absolute_error_margin: 0.01 input: - age: 30 - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 832 - employment_income: 9000 - iidb: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: true - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 832 + employment_income: 9000 + iidb: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: true + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_pensioner: [true, false] + council_tax_reduction_individual_non_dep_deduction: [0, 0] - name: council_tax_reduction excludes each income-related benefit recipient from remunerative work (income-related ESA) - # The recipient is not in remunerative work, so the flat amount applies - # rather than a band. SI 2012/2885 Sch 1 para 8(8)(a) exempts this - # non-dependant altogether; that exemption remains for #2007. + # The recipient is not in remunerative work (SI 2012/2885 reg 10(6)), and + # Sch 1 para 8(8)(a) exempts them altogether: no deduction. period: 2026 absolute_error_margin: 0.01 input: - age: 30 - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 832 - employment_income: 9000 - iidb: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: true + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 832 + employment_income: 9000 + iidb: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: true + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_pensioner: [true, false] + council_tax_reduction_individual_non_dep_deduction: [0, 0] - name: council_tax_reduction excludes each income-related benefit recipient from remunerative work (no excluded benefit) period: 2026 absolute_error_margin: 0.01 input: - age: 30 - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 832 - employment_income: 9000 - iidb: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 832 + employment_income: 9000 + iidb: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_pensioner: [true, false] + council_tax_reduction_individual_non_dep_deduction: [0, 551.20] - name: ENGLAND council_tax_reduction reads its remunerative work hours parameter (16 weekly hours) period: 2026 absolute_error_margin: 0.01 input: gov.local_authorities.england.council_tax_reduction.pensioners.non_dep_deduction.remunerative_work_hours: 20 - age: 30 - country: ENGLAND - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 832 - employment_income: 9000 - maintenance_income: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 832 + employment_income: 9000 + maintenance_income: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_pensioner: [true, false] + council_tax_reduction_individual_non_dep_deduction: [0, 270.40] - name: ENGLAND council_tax_reduction reads its remunerative work hours parameter (20 weekly hours) period: 2026 absolute_error_margin: 0.01 input: gov.local_authorities.england.council_tax_reduction.pensioners.non_dep_deduction.remunerative_work_hours: 20 - age: 30 - country: ENGLAND - council_tax_reduction_individual_non_dep_deduction_eligible: true - hours_worked: 1040 - employment_income: 9000 - maintenance_income: 12000 - social_security_income: 0 - is_on_income_support: false - is_on_income_based_jsa: false - is_on_income_related_esa: false + people: + applicant: + age: 70 + social_security_income: 0 + non_dependant: + age: 30 + hours_worked: 1040 + employment_income: 9000 + maintenance_income: 12000 + social_security_income: 0 + is_on_income_support: false + is_on_income_based_jsa: false + is_on_income_related_esa: false + benunits: + applicant_unit: + members: [applicant] + non_dependant_unit: + members: [non_dependant] + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 + households: + household: + members: [applicant, non_dependant] + country: ENGLAND output: - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_pensioner: [true, false] + council_tax_reduction_individual_non_dep_deduction: [0, 551.20] - name: SCOTLAND council_tax_reduction reads its remunerative work hours parameter (16 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 gov.local_authorities.scotland.council_tax_reduction.non_dep_deduction.remunerative_work_hours: 20 age: 30 country: SCOTLAND @@ -650,12 +1106,20 @@ is_on_income_based_jsa: false is_on_income_related_esa: false output: - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 288.60 - name: SCOTLAND council_tax_reduction reads its remunerative work hours parameter (20 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 gov.local_authorities.scotland.council_tax_reduction.non_dep_deduction.remunerative_work_hours: 20 age: 30 country: SCOTLAND @@ -668,12 +1132,20 @@ is_on_income_based_jsa: false is_on_income_related_esa: false output: - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_individual_non_dep_deduction: 569.40 - name: WALES council_tax_reduction reads its remunerative work hours parameter (16 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 gov.local_authorities.wales.council_tax_reduction.non_dep_deduction.remunerative_work_hours: 20 age: 30 country: WALES @@ -686,12 +1158,20 @@ is_on_income_based_jsa: false is_on_income_related_esa: false output: - council_tax_reduction_individual_non_dep_deduction: 1060.8 + council_tax_reduction_individual_non_dep_deduction: 348.40 - name: WALES council_tax_reduction reads its remunerative work hours parameter (20 weekly hours) period: 2026 absolute_error_margin: 0.01 input: + universal_credit_pre_benefit_cap: 0 + child_tax_credit: 0 + working_tax_credit: 0 + child_benefit: 0 + income_support: 0 + jsa_income: 0 + esa_income: 0 + pension_credit: 0 gov.local_authorities.wales.council_tax_reduction.non_dep_deduction.remunerative_work_hours: 20 age: 30 country: WALES @@ -704,4 +1184,4 @@ is_on_income_based_jsa: false is_on_income_related_esa: false output: - council_tax_reduction_individual_non_dep_deduction: 5470.4 + council_tax_reduction_individual_non_dep_deduction: 694.20 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_credit.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_credit.yaml index 88c83a064e..1a4f081d36 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_credit.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_credit.yaml @@ -251,14 +251,12 @@ guarantee_credit: [3_376, 0] in_receipt_of_guarantee_credit: [true, false] council_tax_reduction_applicable_income: [0, 5_000] - # The national schemes' non-dependant deduction currently uses Housing - # Benefit amounts (20.40 a week here), not the CTR schedules' own amounts - # and exemptions (SI 2012/2885 Sch 1 para 8); this pins that the deduction - # still applies on guarantee credit, not its size. - council_tax_reduction_non_dep_deductions: [1_060.80, 0] - # 2,000 - 1,060.80 - simulated_council_tax_reduction_benunit: [939.20, 0] - council_tax_reduction: 939.20 + # SI 2012/2885 Sch 1 para 8(1)(b): the son has no hours, so the council deducts + # the 2026-27 lowest amount, 5.20 a week, for him outside remunerative work. + council_tax_reduction_non_dep_deductions: [270.40, 0] + # 2,000 - 270.40 + simulated_council_tax_reduction_benunit: [1_729.60, 0] + council_tax_reduction: 1_729.60 - name: Mixed-age couple is not in receipt of guarantee credit, so the capital limit applies period: 2026 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml index ea63047128..e66ef7b8b4 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml @@ -570,10 +570,15 @@ # A single pensioner who reached pensionable age before April 2021: # 256.00 * 52 = 13,312.00. council_tax_reduction_applicable_amount: 13_312 - # 1,800 - 0.2 x (14,980.16 - 13,312.00) = 1,800 - 333.632 = 1,466.368. - # The couple are in her benefit unit, so the model charges no - # non-dependant deduction for them (#2017). - council_tax_benefit: 1_466.368 + # Applying alone, she has the mother and father, a couple outside her + # family, as her non-dependants (SI 2012/2885 reg 9(1), (2)(a)). + # Neither works, so each takes the lowest pensioner amount, £5.20 a + # week, and the couple pays one deduction, the higher (Sch 1 para 8): + # 5.20 x 52 = 270.40. + council_tax_reduction_non_dep_deductions: 270.40 + # 1,800 - 0.2 x (14,980.16 - 13,312.00) - 270.40 + # = 1,800 - 333.632 - 270.40 = 1,195.968. + council_tax_benefit: 1_195.968 - name: A guarantee credit recipient with IIDB has nil Council Tax Reduction income period: 2026 diff --git a/policyengine_uk/tests/shard_durations.toml b/policyengine_uk/tests/shard_durations.toml index 2c52eb6701..a5d21cd607 100644 --- a/policyengine_uk/tests/shard_durations.toml +++ b/policyengine_uk/tests/shard_durations.toml @@ -7,7 +7,10 @@ # all five Python shards of nine runs on 24 October 2026, each test's mean # over the runs: merge queue runs 40050881130, 40059344467, 40063244949, # 40067189795, 40071060766, 40079467297, 40083761750 and 40087883891, and -# pull request run 40095846881. A slow test not here is placed by the hash. +# pull request run 40095846881. The entries for +# test_non_dependant_deduction_properties.py come from pull request run +# 41294689067 alone, below 20 seconds included. A slow test not here is +# placed by the hash. # test_ci_test_shards.py fails if an entry names a test that no longer # exists, so a pull request that renames or removes a listed test should # rename or delete its line here. Regenerate when a shard drifts well above @@ -167,6 +170,7 @@ "policyengine_uk/tests/test_uc_state_pension_properties.py::test_uc_falls_pound_for_pound_in_state_pension" = 96 "policyengine_uk/tests/test_uc_work_related_groups_properties.py::test_qualifying_age_route_moves_only_claimants_past_it" = 96 "policyengine_uk/tests/test_uc_earned_income_before_work_allowance_properties.py::test_more_pay_never_turns_the_childcare_criterion_on" = 96 +"policyengine_uk/tests/test_non_dependant_deduction_properties.py::test_non_dependant_deduction_invariants" = 96 "policyengine_uk/tests/test_legacy_award_work_properties.py::test_screens_match_a_family_by_family_reading" = 95 "policyengine_uk/tests/test_dated_reform_routes.py::test_a_schedule_of_two_changes_matches_the_oracle" = 95 "policyengine_uk/tests/test_uc_state_pension_properties.py::test_state_pension_counts_like_private_pension" = 95 @@ -262,6 +266,7 @@ "policyengine_uk/tests/test_rent_of_sharers_boarders_and_lodgers_properties.py::test_no_op_without_sharers_boarders_or_lodgers" = 72 "policyengine_uk/tests/test_state_pension_age.py::test_status_matches_statute_for_every_birth_date[2017]" = 72 "policyengine_uk/tests/test_pawhp_household_income_properties.py::test_switching_pawhp_off_lowers_household_income_by_pawhp" = 72 +"policyengine_uk/tests/test_non_dependant_deduction_properties.py::test_capped_universal_credit_matches_the_model" = 72 "policyengine_uk/tests/test_process_time_reforms.py::test_a_triple_lock_rate_set_by_reform_uprates_the_state_pension[via_parameter_changes-generated-bare-year]" = 71 "policyengine_uk/tests/test_state_pension_age.py::test_status_matches_statute_for_every_birth_date[2019]" = 71 "policyengine_uk/tests/test_labour_supply_response_baseline.py::test_basic_rate_rise_gives_responses_of_the_implied_sign" = 71 @@ -372,6 +377,7 @@ "policyengine_uk/tests/test_uc_statutory_pay_properties.py::test_uc_does_not_increase_as_statutory_paternity_pay_rises" = 48 "policyengine_uk/tests/test_salary_sacrifice_ni_only.py::test_explicit_actual_return_matches_actual_pay_and_member_inputs[personal_allowance]" = 48 "policyengine_uk/tests/test_income_support_eligibility_properties.py::test_more_work_or_jsa_never_makes_a_family_eligible" = 48 +"policyengine_uk/tests/test_non_dependant_deduction_properties.py::test_earned_income_exemption_reads_universal_credit" = 48 "policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py::test_disregard_is_non_decreasing_in_earnings" = 47 "policyengine_uk/tests/test_benefit_cap_uc_and_hb_properties.py::test_a_family_with_both_awards_is_capped_once_through_universal_credit" = 47 "policyengine_uk/tests/test_legacy_award_readers_properties.py::test_other_members_esa_never_changes_non_dependant_exemption" = 47 @@ -579,3 +585,7 @@ "policyengine_uk/tests/test_disable_simulated_benefits_winter_heating.py::test_a_household_that_moves_is_paid_under_each_years_scheme[LONDON-SCOTLAND]" = 20 "policyengine_uk/tests/test_capital_gains_responses.py::test_revenue_falls_short_of_the_static_estimate" = 20 "policyengine_uk/tests/test_capital_gains_response_default.py::test_employer_cost_branches_hold_the_realisation_response" = 20 +"policyengine_uk/tests/test_non_dependant_deduction_properties.py::test_band_edges_are_inclusive[WALES]" = 15 +"policyengine_uk/tests/test_non_dependant_deduction_properties.py::test_band_edges_are_inclusive[SCOTLAND]" = 12 +"policyengine_uk/tests/test_non_dependant_deduction_properties.py::test_band_edges_are_inclusive[HOUSING_BENEFIT]" = 10 +"policyengine_uk/tests/test_non_dependant_deduction_properties.py::test_band_edges_are_inclusive[ENGLAND]" = 9 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 index 5976d26204..b171878534 100644 --- a/policyengine_uk/tests/test_benefit_cap_lha_scheme_split_properties.py +++ b/policyengine_uk/tests/test_benefit_cap_lha_scheme_split_properties.py @@ -38,10 +38,10 @@ 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. + ``housing_benefit_LHA_allowed_bedrooms``), which can differ from the + Universal Credit count: it gives a bedroom to a qualifying young person + a local authority looks after, whom UC Regs 2013 Sch 4 para 9(2)(g) + leaves out, and pools the children of other occupiers' families. 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 @@ -874,13 +874,12 @@ def test_examples_reach_the_cases(): 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). + # Both schemes' size criteria give the young person a bedroom: HB as a + # person who is not a child (reg. 13D(3)(b)), UC as a non-dependant who + # is not a child (Sch 4 paras 9(2) and 10(1)(c)). assert v["housing_benefit_LHA_category"][lone_parent] == "C" - assert ( - v["LHA_allowed_bedrooms"][lone_parent] - != v["housing_benefit_LHA_allowed_bedrooms"][lone_parent] - ) + assert v["LHA_allowed_bedrooms"][lone_parent] == 2 + assert v["housing_benefit_LHA_allowed_bedrooms"][lone_parent] == 2 # 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" diff --git a/policyengine_uk/tests/test_benefit_unit_non_dependant_properties.py b/policyengine_uk/tests/test_benefit_unit_non_dependant_properties.py new file mode 100644 index 0000000000..bf211f4a82 --- /dev/null +++ b/policyengine_uk/tests/test_benefit_unit_non_dependant_properties.py @@ -0,0 +1,578 @@ +"""Property-based tests for non-dependants within a benefit unit. + +A benefit unit can include members aged 16 or over who are neither the +claimant or partner nor a qualifying young person: in law they are +non-dependants of the renter (UC Regs 2013 Sch 4 para 9; HB Regs 2006 reg 3; +CTR SI 2012/2885 reg 9 and equivalents). Invariants, for any generated +population of households: + +1. Oracle: each family's Universal Credit, Housing Benefit and Council Tax + Reduction non-dependant deductions, and who is eligible for an individual + deduction, equal an independent calculation from the generated household + structure: who is a non-dependant, of whom, how a non-dependant couple + pays one deduction (the higher), and how a non-dependant of several joint + occupiers is apportioned (by rent or liability share, whichever family + the non-dependant is in). +2. Conservation: every Housing Benefit non-dependant deduction (a couple's + counted once) is borne in full across the joint occupiers, and once more + by a boarder or lodger for a non-dependant in its own benefit unit, + unless a claimant is exempt; each Council Tax Reduction claiming family + bears every non-dependant, under its own scheme, in proportion to its + joint-liability share + unless the applicant is exempt; every Universal Credit contribution is + borne by exactly one family unless that family's renter is exempt. +3. Exemptions: a family whose renter is exempt (UC Sch 4 para 15; HB reg + 74(6); CTR Sch 1 para 8(6)) has no deductions; no deduction is charged for + a non-dependant under 21 (UC) or under 18 (HB, CTR), or for a qualifying + young person. +4. No-op: in households without non-dependants inside a benefit unit, the + deductions equal the previous formulas, which charged only members of + other families. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_core.periods import period as make_period + +from policyengine_uk import Simulation +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction._national_non_dependants import ( + household_head_family_is_pensioner, + individual_non_dep_deduction, +) + +# Situation inputs without a period key are set for 2025, so the properties +# are checked in 2025, when the contribution was £93.02 a month. +YEAR = 2025 +UC_CONTRIBUTION = 93.02 * 12 +PROPERTY_SETTINGS = settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +# Members of a benefit unit who are not the claimant or partner. +# dependant: a qualifying young person the claimant is responsible for; +# looked_after: a qualifying young person placed by a local authority, for +# whom no one in the family is responsible; +# non_dependant: aged 16 or over and not a qualifying young person. +KINDS = ["dependant", "looked_after", "non_dependant"] +ROLES = ["sharer", "lodger", "non_dependant"] + + +@st.composite +def extra_member(draw): + kind = draw(st.sampled_from(KINDS)) + age = draw(st.integers(16, 19) if kind != "non_dependant" else st.integers(16, 60)) + return dict( + kind=kind, + age=age, + earnings=draw(st.sampled_from([0, 2_600, 15_600, 30_000])), + hours=draw(st.sampled_from([0, 10, 37.5])), + attendance_allowance=draw(st.booleans()), + ) + + +@st.composite +def family(draw, role): + # The household head's family has the household's oldest members, so it + # is unambiguously the head's (and the council tax claimant's) family. + adult_age = st.integers(61, 90) if role == "head" else st.integers(25, 59) + return dict( + role=role, + adult_ages=draw(st.lists(adult_age, min_size=1, max_size=2)), + extras=draw(st.lists(extra_member(), min_size=0, max_size=2)), + child=draw(st.booleans()), + renter_pip=draw(st.booleans()), + # Take-up: a family flagged not to claim Universal Credit passes its + # in-unit non-dependants to the claim that counts the household's. + would_claim_uc=draw(st.booleans()), + payment=draw(st.floats(1_000, 10_000, allow_nan=False)), + ) + + +@st.composite +def household(draw): + head = draw(family("head")) + others = [ + draw(family(draw(st.sampled_from(ROLES)))) + for _ in range(draw(st.integers(0, 2))) + ] + return dict( + families=[head] + others, + rent=draw(st.floats(3_000, 20_000, allow_nan=False)), + tenure=draw(st.sampled_from(["RENT_FROM_COUNCIL", "RENT_PRIVATELY"])), + ) + + +population = st.lists(household(), min_size=1, max_size=5) + + +def build(population, convert=None): + """One situation for the whole population, and a record per person and + per family for the oracle. convert maps extra members' kinds to others.""" + people, benunits, homes = {}, {}, {} + person_rows, family_rows = [], [] + for h, house in enumerate(population): + members = [] + for f, fam in enumerate(house["families"]): + name = f"h{h}_f{f}" + ids = [] + head_family = f == 0 + # The head's family has the household's oldest member. + for i, age in enumerate(fam["adult_ages"]): + pid = f"{name}_adult_{i}" + pip = fam["renter_pip"] and i == 0 + people[pid] = { + "age": age, + "is_claimant_or_partner": True, + "is_household_head": head_family and i == 0, + "pip_dl": 3_000.0 if pip else 0.0, + } + ids.append(pid) + person_rows.append( + dict( + household=h, + family=len(family_rows), + kind="adult", + age=age, + disability_benefit=pip, + ) + ) + for i, extra in enumerate(fam["extras"]): + kind = (convert or {}).get(extra["kind"], extra["kind"]) + pid = f"{name}_extra_{i}" + young = kind != "non_dependant" + people[pid] = { + "age": min(extra["age"], 19) if young else extra["age"], + "is_claimant_or_partner": False, + "is_household_head": False, + "is_qualifying_young_person_for_universal_credit": young, + "is_child_or_qualifying_young_person_for_child_benefit": young, + "is_looked_after_by_local_authority": kind == "looked_after", + "employment_income": float(extra["earnings"]), + "weekly_hours": float(extra["hours"]), + "attendance_allowance": ( + 3_000.0 if extra["attendance_allowance"] else 0.0 + ), + } + ids.append(pid) + person_rows.append( + dict( + household=h, + family=len(family_rows), + kind=kind, + age=people[pid]["age"], + disability_benefit=extra["attendance_allowance"], + ) + ) + if fam["child"]: + pid = f"{name}_child" + # Inputs set for some members are inputs for all, so the + # child's Child Benefit status is stated, not left to default. + people[pid] = { + "age": 8, + "is_claimant_or_partner": False, + "is_household_head": False, + "is_child_or_qualifying_young_person_for_child_benefit": True, + } + ids.append(pid) + person_rows.append( + dict( + household=h, + family=len(family_rows), + kind="child", + age=8, + disability_benefit=False, + ) + ) + benunit = {"members": ids, "would_claim_uc": fam["would_claim_uc"]} + if fam["role"] == "sharer": + benunit["liable_for_share_of_household_rent"] = True + if fam["role"] == "lodger": + people[ids[0]]["rent_paid_as_lodger"] = fam["payment"] + benunits[name] = benunit + members.extend(ids) + family_rows.append( + dict( + household=h, + role=fam["role"], + renter_pip=fam["renter_pip"], + n_liable=len(fam["adult_ages"]), + ) + ) + homes[f"h{h}"] = { + "members": members, + "rent": house["rent"], + "tenure_type": house["tenure"], + "brma": "MAIDSTONE", + } + situation = {"people": people, "benunits": benunits, "households": homes} + return situation, person_rows, family_rows + + +def calc(sim, variable, map_to=None): + return np.asarray(sim.calculate(variable, YEAR, map_to=map_to), dtype=float) + + +def counted_once_per_couple(person_rows, amounts): + """A couple pays one deduction, the higher (HB reg 74(3); CTR Sch 1 para + 8(3)): keep the larger amount of each family's adults (its claimant and + partner) and zero the other; anyone else counts in full.""" + counted = np.array(amounts, dtype=float) + adults = {} + for p, row in enumerate(person_rows): + if row["kind"] == "adult": + adults.setdefault(row["family"], []).append(p) + for members in adults.values(): + keep = max(members, key=lambda p: (counted[p], -p)) + for p in members: + if p != keep: + counted[p] = 0.0 + return counted + + +def oracle( + person_rows, + family_rows, + uc, + hb, + ctr, + pension_credit, + claims, + ctr_share, + uc_counted, + uc_own_claim, + ctr_other, + ctr_own_scheme_is_heads, +): + """Expected deductions per family from the generated structure. + + uc, hb and ctr are each person's individual deduction from the model; + the oracle decides independently who is whose non-dependant. Which + Universal Credit claim counts the household's non-dependants + (uc_non_dependants_counted), whether a family makes its own claim, the + CTR claim flags and the joint-liability shares are read from the model. + ctr is each person's deduction under the scheme of the household head's + family; ctr_other under the other scheme, which a claiming family on that + scheme bears (SI 2012/2885 reg 3). + """ + n = len(family_rows) + exp_uc, exp_hb, exp_ctr = np.zeros(n), np.zeros(n), np.zeros(n) + expected_uc_individual = np.zeros(len(person_rows)) + hb = counted_once_per_couple(person_rows, hb) + ctr = counted_once_per_couple(person_rows, ctr) + ctr_other = counted_once_per_couple(person_rows, ctr_other) + households = sorted({f["household"] for f in family_rows}) + for h in households: + fams = [i for i, f in enumerate(family_rows) if f["household"] == h] + # Sch 4 para 9(2)(f): one claim counts the household's non-dependants. + counted = [i for i in fams if uc_counted[i]] + counted_family = counted[0] if counted else None + # Families liable for the household's rent: the head's and sharers'. + liable = [i for i in fams if family_rows[i]["role"] in ("head", "sharer")] + total_liable = sum(family_rows[i]["n_liable"] for i in liable) + share = { + i: (family_rows[i]["n_liable"] / total_liable if i in liable else 0.0) + for i in fams + } + for p, row in enumerate(person_rows): + if row["household"] != h: + continue + f = row["family"] + role = family_rows[f]["role"] + other_family = role == "non_dependant" + own_family = row["kind"] == "non_dependant" + # UC: a member of a family not liable for rent counts in the + # claim that counts the household's non-dependants; a non-QYP + # member aged 16+ of any other family counts in their own family's + # claim if it makes one, otherwise in that same counted claim. + # Para 16: Attendance Allowance or PIP daily living, or Pension + # Credit, which only a claimant or partner receives. + uc_exempt = row["disability_benefit"] or ( + row["kind"] == "adult" and pension_credit[f] > 0 + ) + uc_non_dependant = other_family or own_family + if uc_non_dependant and row["age"] >= 21 and not uc_exempt: + expected_uc_individual[p] = UC_CONTRIBUTION + own_claim = own_family and not other_family and uc_own_claim[f] + bearer = f if own_claim else counted_family + if ( + uc_non_dependant + and bearer is not None + and not family_rows[bearer]["renter_pip"] + ): + exp_uc[bearer] += uc[p] + # HB: every non-dependant, in any family, is pooled across the + # joint occupiers by rent share; a boarder or lodger, who is not + # a joint occupier, also bears its own in full. + if other_family or own_family: + for i in fams: + exp_hb[i] += share[i] * hb[p] + if own_family and role == "lodger": + exp_hb[f] += hb[p] + # CTR: every non-dependant, a lodger's included, is pooled across + # the claiming families by joint-liability share, except that a + # family's own claimant or partner is not its non-dependant. + if other_family or own_family: + for i in fams: + if i == f and not own_family: + continue + amount = ctr[p] if ctr_own_scheme_is_heads[i] else ctr_other[p] + exp_ctr[i] += claims[i] * ctr_share[i] * amount + # A claimant or partner on PIP daily living exempts their own family + # from all HB and CTR deductions (reg 74(6); Sch 1 para 8(6)). + for i in fams: + if family_rows[i]["renter_pip"]: + exp_hb[i] = 0.0 + exp_ctr[i] = 0.0 + return exp_uc, exp_hb, exp_ctr, expected_uc_individual + + +def ctr_by_scheme(sim, person_rows, family_rows): + """Each person's CTR deduction under the other scheme (pensioner or + working-age) from the household head's family, and whether each family is + on the head's scheme. Computed with the aggregator's own helper, since a + jointly liable applicant on the other scheme has its deductions + recomputed under that scheme.""" + person = sim.populations["person"] + period = make_period(YEAR) + head_pensioner = np.asarray(household_head_family_is_pensioner(person, period)) + eligible = ( + calc(sim, "council_tax_reduction_individual_non_dep_deduction_eligible") > 0 + ) + other = np.asarray( + individual_non_dep_deduction( + person, period, sim.tax_benefit_system.parameters, ~head_pensioner + ), + dtype=float, + ) + first_member = {} + for p, row in enumerate(person_rows): + first_member.setdefault(row["family"], p) + family_head_pensioner = np.array( + [head_pensioner[first_member[f]] for f in range(len(family_rows))] + ) + family_pensioner = calc(sim, "council_tax_reduction_pensioner") > 0 + return dict( + ctr_other=eligible * other, + ctr_own_scheme_is_heads=family_pensioner == family_head_pensioner, + ) + + +def pool_under_own_scheme(person_rows, family_rows, v, mask=None): + """For each family, the household's CTR deductions (a couple's once) + under that family's own scheme, over the members in mask (everyone by + default).""" + mask = np.ones(len(person_rows), dtype=bool) if mask is None else mask + person_household = np.array([row["household"] for row in person_rows]) + family_household = np.array([row["household"] for row in family_rows]) + pools = {} + for scheme, amounts in (("head", v["ctr"]), ("other", v["ctr_other"])): + counted = counted_once_per_couple(person_rows, amounts) * mask + by_household = np.bincount( + person_household, weights=counted, minlength=family_household.max() + 1 + ) + pools[scheme] = by_household[family_household] + return np.where(v["ctr_own_scheme_is_heads"], pools["head"], pools["other"]) + + +def run(population, convert=None): + situation, person_rows, family_rows = build(population, convert) + sim = Simulation(situation=situation) + values = dict( + uc=calc(sim, "uc_individual_non_dep_deduction"), + hb=calc(sim, "household_benefits_individual_non_dep_deduction"), + ctr=calc(sim, "council_tax_reduction_individual_non_dep_deduction"), + pension_credit=calc(sim, "pension_credit"), + claims=calc(sim, "council_tax_reduction_claimant_benunit"), + ctr_share=calc(sim, "council_tax_reduction_joint_liability_share"), + uc_counted=calc(sim, "uc_non_dependants_counted") > 0, + **ctr_by_scheme(sim, person_rows, family_rows), + uc_own_claim=( + (calc(sim, "benunit_is_rent_liable") > 0) + & (calc(sim, "is_uc_eligible") > 0) + & (calc(sim, "claims_universal_credit") > 0) + ), + ) + return sim, person_rows, family_rows, values + + +@PROPERTY_SETTINGS +@given(population) +def test_deductions_match_the_oracle(population): + sim, person_rows, family_rows, v = run(population) + exp_uc, exp_hb, exp_ctr, exp_uc_individual = oracle(person_rows, family_rows, **v) + # 1. Oracle. The members each scheme classes as in-unit non-dependants + # are exactly the generated non-QYP members. + kinds = np.array([row["kind"] for row in person_rows]) + for in_unit_variable in ( + "is_benefit_unit_non_dependant_for_universal_credit", + "is_benefit_unit_non_dependant_for_legacy_benefits", + "is_benefit_unit_non_dependant_for_council_tax_reduction", + ): + assert np.array_equal( + calc(sim, in_unit_variable) > 0, kinds == "non_dependant" + ), in_unit_variable + assert np.allclose(v["uc"], exp_uc_individual, atol=0.01) + assert np.allclose(calc(sim, "uc_non_dep_deductions"), exp_uc, atol=0.01) + assert np.allclose( + calc(sim, "housing_benefit_non_dep_deductions"), exp_hb, atol=0.01 + ) + assert np.allclose( + calc(sim, "council_tax_reduction_non_dep_deductions"), exp_ctr, atol=0.01 + ) + + # HB and CTR eligibility: a non-dependant aged 18 or over, either in a + # family not liable for rent or within their own benefit unit. + def eligible(own_family_roles): + return np.array( + [ + row["age"] >= 18 + and ( + family_rows[row["family"]]["role"] == "non_dependant" + or ( + row["kind"] == "non_dependant" + and family_rows[row["family"]]["role"] in own_family_roles + ) + ) + for row in person_rows + ] + ) + + # Any family's own non-dependants, a boarder's or lodger's included. HB + # eligibility also excludes an exempt non-dependant (reg 74(7)-(10)); CTR + # applies its exemptions in the amount. + everyone = eligible(("head", "sharer", "lodger", "non_dependant")) + hb_exempt = calc(sim, "housing_benefit_non_dep_deduction_exempt") > 0 + assert np.array_equal( + calc(sim, "housing_benefit_individual_non_dep_deduction_eligible") > 0, + everyone & ~hb_exempt, + ) + assert np.array_equal( + calc(sim, "council_tax_reduction_individual_non_dep_deduction_eligible") > 0, + everyone, + ) + + +@PROPERTY_SETTINGS +@given(population) +def test_conservation_and_exemptions(population): + sim, person_rows, family_rows, v = run(population) + ages = np.array([row["age"] for row in person_rows]) + kinds = np.array([row["kind"] for row in person_rows]) + # 2. Conservation. HB: each deduction, a couple's once, is borne in full + # across the joint occupiers, and a lodger's own non-dependant once more + # by the lodger, where no claimant is exempt. + hb_counted = counted_once_per_couple(person_rows, v["hb"]) + hb_families = sim.map_result( + calc(sim, "housing_benefit_non_dep_deductions"), "benunit", "household" + ) + lodgers_own = np.array( + [ + row["kind"] == "non_dependant" + and family_rows[row["family"]]["role"] == "lodger" + for row in person_rows + ] + ) + hb_people = sim.map_result(hb_counted * (1 + lodgers_own), "person", "household") + hb_exempt = ( + sim.map_result( + calc(sim, "housing_benefit_non_dep_deductions_claimant_exempt"), + "benunit", + "household", + ) + > 0 + ) + assert np.allclose(hb_families[~hb_exempt], hb_people[~hb_exempt], atol=0.01) + assert np.all(hb_families <= hb_people + 0.01) + # CTR: every non-dependant is borne by each claiming family, under its + # own scheme, in proportion to its joint-liability share (which follows + # the regulations' per-person wording, so shares need not sum to one); + # none where the applicant is exempt. This holds on the generator's + # domain (rent > 0, claimants liable for it), where no claiming family's + # own claimant or partner has a deduction. + ctr_families = calc(sim, "council_tax_reduction_non_dep_deductions") + expected_ctr = ( + v["claims"] + * v["ctr_share"] + * pool_under_own_scheme(person_rows, family_rows, v) + ) + ctr_exempt = calc(sim, "council_tax_reduction_applicant_has_non_dep_exemption") > 0 + assert np.allclose(ctr_families[~ctr_exempt], expected_ctr[~ctr_exempt], atol=0.01) + assert np.all(ctr_families[ctr_exempt] == 0) + # UC: each contribution is borne once, except where the bearer is exempt. + uc_families = sim.map_result( + calc(sim, "uc_non_dep_deductions"), "benunit", "household" + ) + uc_people = sim.map_result(v["uc"], "person", "household") + assert np.all(uc_families <= uc_people + 0.01) + # 3. Exemptions. + renter_exempt = calc(sim, "uc_non_dep_deductions_renter_exempt") > 0 + assert np.all(calc(sim, "uc_non_dep_deductions")[renter_exempt] == 0) + hb_claimant_exempt = ( + calc(sim, "housing_benefit_non_dep_deductions_claimant_exempt") > 0 + ) + assert np.all( + calc(sim, "housing_benefit_non_dep_deductions")[hb_claimant_exempt] == 0 + ) + assert np.all(v["uc"][ages < 21] == 0) + assert np.all(v["hb"][ages < 18] == 0) + assert np.all(v["ctr"][ages < 18] == 0) + # Nobody in a family liable for rent is charged except its own + # non-dependants. + not_non_dependant = kinds != "non_dependant" + liable_family = np.array( + [family_rows[row["family"]]["role"] != "non_dependant" for row in person_rows] + ) + for amounts in (v["uc"], v["hb"], v["ctr"]): + assert np.all(amounts[not_non_dependant & liable_family] == 0) + + +@PROPERTY_SETTINGS +@given(population) +def test_no_op_without_non_dependants_in_a_benefit_unit(population): + every_kind_dependant = {kind: "dependant" for kind in KINDS} + sim, person_rows, family_rows, v = run(population, every_kind_dependant) + # 4. The previous formulas: only members of other families (a couple's + # higher amount once), charged to the claim that counts them (UC), by rent share + # (HB) or by claim share (CTR), with the claimants' exemptions. + person_household = np.array([row["household"] for row in person_rows]) + family_household = np.array([row["household"] for row in family_rows]) + other = calc(sim, "is_non_dependant_of_household_head") > 0 + + def other_families(amounts): + by_household = np.bincount( + person_household, + weights=amounts * other, + minlength=family_household.max() + 1, + ) + return by_household[family_household] + + renter_exempt = calc(sim, "uc_non_dep_deductions_renter_exempt") > 0 + assert np.allclose( + calc(sim, "uc_non_dep_deductions"), + np.where(renter_exempt, 0, v["uc_counted"] * other_families(v["uc"])), + atol=0.01, + ) + hb_claimant_exempt = ( + calc(sim, "housing_benefit_non_dep_deductions_claimant_exempt") > 0 + ) + hb_counted = counted_once_per_couple(person_rows, v["hb"]) + assert np.allclose( + calc(sim, "housing_benefit_non_dep_deductions"), + np.where( + hb_claimant_exempt, + 0, + calc(sim, "share_of_household_rent") * other_families(hb_counted), + ), + atol=0.01, + ) + ctr_exempt = calc(sim, "council_tax_reduction_applicant_has_non_dep_exemption") > 0 + pool = pool_under_own_scheme(person_rows, family_rows, v, mask=other) + assert np.allclose( + calc(sim, "council_tax_reduction_non_dep_deductions"), + np.where(ctr_exempt, 0, v["claims"] * v["ctr_share"] * pool), + atol=0.01, + ) diff --git a/policyengine_uk/tests/test_council_tax_reduction_claimant_properties.py b/policyengine_uk/tests/test_council_tax_reduction_claimant_properties.py index 37e6168269..67556a7aaf 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_claimant_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_claimant_properties.py @@ -33,10 +33,13 @@ 3. Simulated reductions: no family outside the claimants gets one, and a household's total never exceeds its council tax. (Reported reductions, used where a scheme is not modelled, are outside this test.) -4. Non-dependants: no claimant or partner of a claimant family is a - non-dependant (the applicant's family, SI 2012/2885 reg 9(2)(a)). Pin: no - member of a sharer family is one, and every adult outside the claimant - families, the sharer families and boarders' and lodgers' families is one. +4. Non-dependants: no applicant or partner of a claimant family + (is_council_tax_reduction_applicant_or_partner) is a non-dependant (the + applicant's family, SI 2012/2885 reg 9(2)(a)). Pin: no member of a sharer + family is one except a member outside its applicant's family + (is_benefit_unit_non_dependant_for_council_tax_reduction), and every + adult outside the claimant families, the sharer families and boarders' + and lodgers' families is one. 5. Differential: where the input flags exactly one member of a household, or no household in the simulation has the input (so the flag's formula picks the eldest), the family holding the head is the one holding the @@ -318,8 +321,10 @@ def test_claimant_invariants(population): person_sharer = sharer[facts["benunit"]] lodger = calc(sim, "pays_rent_to_householder") adult = age >= 18 - assert not np.any(non_dep & person_claimant & claimant_or_partner) - assert not np.any(non_dep & person_sharer) + applicant_or_partner = calc(sim, "is_council_tax_reduction_applicant_or_partner") + in_unit = calc(sim, "is_benefit_unit_non_dependant_for_council_tax_reduction") + assert not np.any(non_dep & person_claimant & applicant_or_partner) + assert not np.any(non_dep & person_sharer & ~in_unit) assert np.all(non_dep[adult & ~person_claimant & ~person_sharer & ~lodger]) # 5. Differential with the person-level household head flag. Once anyone in diff --git a/policyengine_uk/tests/test_household_head_agreement_properties.py b/policyengine_uk/tests/test_household_head_agreement_properties.py index 4862578ad5..26135828ed 100644 --- a/policyengine_uk/tests/test_household_head_agreement_properties.py +++ b/policyengine_uk/tests/test_household_head_agreement_properties.py @@ -28,13 +28,17 @@ if and only if their family is neither the head's nor liable for rent. - Universal Credit non-dependants: at most one family in a household counts them (uc_non_dependants_counted; UC Regs 2013 Sch 4 para - 9(2)(f)). Pin: deductions fall only on that family's claim, and where - the head's family is liable for the rent and claims Universal Credit, - it is the head's. Only families with a share of the rent have Housing - Benefit non-dependant deductions. + 9(2)(f)). Pin: other families' non-dependants fall only on that + family's claim, and a family's own in-unit non-dependants on its own + claim; where the head's family is liable for the rent and claims + Universal Credit, the counting claim is the head's. Only families with + a share of the rent have Housing Benefit non-dependant deductions, + except a boarder or lodger for a non-dependant in its own benefit unit. - Council Tax Reduction: only the head's family and sharers claim; where the head is 18 or over, the head's family claims and no one in it is a - non-dependant. + non-dependant except a member who is outside the applicant's family + (is_benefit_unit_non_dependant_for_council_tax_reduction; SI 2012/2885 + reg 9(2)(a)). - A family paying the householder as boarders or lodgers is never the head's, and the maintenance loan never has the head living with parents. 4. Metamorphic: every value the simulation computes, other than the @@ -395,7 +399,11 @@ def test_every_programme_reads_one_head(population): # counts the household's non-dependants: the head's family's wherever it # is liable for the rent and claims. counted = calc(sim, "uc_non_dependants_counted") - assert np.all(counted[calc(sim, "uc_non_dep_deductions") > 0]) + # A family's own in-unit non-dependants count in its own claim. + has_uc_in_unit = any_in_benunit( + calc(sim, "is_benefit_unit_non_dependant_for_universal_credit"), facts + ) + assert np.all((counted | has_uc_in_unit)[calc(sim, "uc_non_dep_deductions") > 0]) household_of_benunit = np.zeros(head_family.size, dtype=int) household_of_benunit[benunit_of] = facts["household"] assert np.all(sum_in_household(counted, household_of_benunit) <= 1) @@ -409,7 +417,16 @@ def test_every_programme_reads_one_head(population): in_household = head_claims_uc[household_of_benunit] assert np.array_equal(counted[in_household], head_family[in_household]) # Housing Benefit non-dependant deductions. - assert np.all(calc(sim, "housing_benefit_non_dep_deductions")[share == 0] == 0) + # A boarder or lodger bears a non-dependant in its own benefit unit. + has_legacy_in_unit = any_in_benunit( + calc(sim, "is_benefit_unit_non_dependant_for_legacy_benefits"), facts + ) + assert np.all( + calc(sim, "housing_benefit_non_dep_deductions")[ + (share == 0) & ~has_legacy_in_unit + ] + == 0 + ) # Council Tax Reduction. claimant = calc(sim, "council_tax_reduction_claimant_benunit") assert not np.any(claimant & ~head_family & ~sharer) @@ -418,7 +435,8 @@ def test_every_programme_reads_one_head(population): ctr_non_dependant = calc( sim, "council_tax_reduction_individual_non_dep_deduction_eligible" ) - assert not np.any(ctr_non_dependant & adult_head_family[benunit_of]) + ctr_in_unit = calc(sim, "is_benefit_unit_non_dependant_for_council_tax_reduction") + assert not np.any(ctr_non_dependant & adult_head_family[benunit_of] & ~ctr_in_unit) # Boarders and lodgers; the maintenance loan. assert not np.any(calc(sim, "pays_rent_to_householder") & person_head_family) arrangement = calc(sim, "maintenance_loan_living_arrangement") diff --git a/policyengine_uk/tests/test_housing_benefit_non_dep_history.py b/policyengine_uk/tests/test_housing_benefit_non_dep_history.py index 47d4e369b0..0c45be6875 100644 --- a/policyengine_uk/tests/test_housing_benefit_non_dep_history.py +++ b/policyengine_uk/tests/test_housing_benefit_non_dep_history.py @@ -42,7 +42,7 @@ def test_all_historical_scale_cells(year, thresholds, amounts): # SI 2015/457 art 18(3); SI 2016/242 reg 5; SI 2017/260 art 22(4); # SI 2018/281 arts 1(2)(h), 23(3) and 24(3). - # Test the parameter itself, not the separate #2007 eligibility/banding fix. + # Test the parameter itself, not eligibility or banding. scale = load_parameter_file(str(PATH), "hb_non_dep_scale")(f"{year}-04-30") assert scale.thresholds == thresholds assert scale.amounts == amounts diff --git a/policyengine_uk/tests/test_lha_bedroom_young_people_properties.py b/policyengine_uk/tests/test_lha_bedroom_young_people_properties.py new file mode 100644 index 0000000000..e622ff5a98 --- /dev/null +++ b/policyengine_uk/tests/test_lha_bedroom_young_people_properties.py @@ -0,0 +1,623 @@ +"""Property-based tests for the bedrooms of benefit-unit members aged 16 or +over who are not the claimant or partner (UC Regs 2013 Sch 4 paras 9-10; HB +Regs 2006 and HB (SPC) Regs 2006 reg 13D(3)). + +Invariants, for any generated population of households: + +1. Monotonicity: adding a person aged 16 to 19 to any family never lowers + any family's Universal Credit or Housing Benefit bedrooms, whether the + claimant and partner are supplied or inferred. +2. Exact effect: when the added person is not the claimant or partner, their + family gains one Housing Benefit bedroom, and one Universal Credit bedroom + unless they are a qualifying young person no one is responsible for (para + 9(2)(g)) or they head the household and are not a qualifying young person + (the formula treats the household head as liable for the rent, so such + a member is neither a renter nor a non-dependant: paras 1(2), 9(1)(a) and + 9(2)(d)). If they join a non-dependant's family, the claim that counts the + household's non-dependants (uc_non_dependants_counted) gains one Universal + Credit bedroom unless they are a qualifying young person no one is + responsible for (para 9(2)(g) applies to the whole extended benefit unit), + and every family liable for the rent gains one Housing Benefit bedroom, + since they are an occupier of each such claim (reg 13D(12)). If they join + a boarder's or lodger's family, every family liable for the rent gains one + Housing Benefit bedroom. If they are an in-unit non-dependant (invariant + 4) of a family that makes no Universal Credit claim of its own, the claim + that counts the household's non-dependants gains one Universal Credit + bedroom too, as it bears their contribution. No other family's bedrooms + change. +3. Reference: every family's bedrooms equal an independent count of the size + criteria. Universal Credit: one for the renter or couple; one for each + other member aged 16 or over, except a qualifying young person no one is + responsible for and a household head who is not a qualifying young + person; for the counting claim, one for each person aged 16 or + over in a non-dependant's family, with the same exception, and one for + each in-unit non-dependant of a family that makes no claim; and the + fewest rooms that hold the family's own children. Housing Benefit, for a + family liable for the rent: one for the claimant or couple; one for each + other member aged 16 or over; for each non-dependant's, boarder's or + lodger's family, one for its couple or single adult (reg 13D(3)(a), (b)) + and one for each other member aged 16 or over; and the fewest rooms that + hold the children of all those occupiers together. Any other family's + Housing Benefit claim counts only its own members. The fewest rooms hold + children under 16 two to a room, where only children of the same sex or + two children under 10 may share, found by brute force. Which claim counts + the non-dependants, and which families are liable for the rent, are read + from the model; they are tested elsewhere. Universal Credit does not yet + count other families' children (a separate change). +4. In-unit non-dependants: every member of a benefit unit aged 16 or over + who is neither the claimant nor partner nor a Universal Credit qualifying + young person nor the household head (the householder in whose name the + accommodation is rented, whom the formula reads as liable for the rent, + so that para 9(2)(d) excludes them) is a non-dependant (para 9(2)) with + a bedroom (para 10(1)(c)). So their family has one more Universal Credit + bedroom with them than without them, and making them qualifying young + persons the renter is responsible for (para 10(1)(b)) leaves every family's + Universal Credit bedrooms unchanged, except that the claim counting the + household's non-dependants loses the bedroom of one whose own family + makes no claim and is not that claim (para 9(2)(g)). The model's + is_benefit_unit_non_dependant_for_universal_credit, which the formula + reads, marks exactly these members. +5. Rooms and contributions: nobody owes a housing cost contribution without + being a non-dependant (para 13). Adding an adult aged 16 to 64, not in + education, to any family: if they are an in-unit non-dependant, they owe + a contribution unless aged under 21 or receiving Attendance Allowance + (para 16(2)); the claim that bears it (their own family's if it makes a + claim, otherwise the one that counts the household's non-dependants) + gains exactly that contribution, unless its renter is exempt (para 15), + and one bedroom; no other family's contributions change. +6. Coding: moving such an adult, not claiming, between a family and a + benefit unit of their own leaves every claiming family's Universal Credit + bedrooms, contributions and award unchanged. +""" + +from itertools import combinations + +import numpy as np +from hypothesis import HealthCheck, example, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +YEAR = 2025 # Situation inputs without a period are set for 2025. +PROPERTY_SETTINGS = settings( + max_examples=25, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +ROLES = ["sharer", "boarder", "lodger", "non_dependant"] +EDUCATION = ["NOT_IN_EDUCATION", "UPPER_SECONDARY", "TERTIARY"] + +child = st.tuples(st.integers(0, 15), st.booleans()) # (age, is_male) + + +@st.composite +def family(draw, role): + return dict( + role=role, + adults=draw(st.lists(st.integers(20, 85), min_size=1, max_size=2)), + children=draw(st.lists(child, min_size=0, max_size=4)), + ) + + +@st.composite +def household(draw): + others = draw( + st.lists(st.sampled_from(ROLES).flatmap(family), min_size=0, max_size=2) + ) + return [draw(family("head"))] + others + + +@st.composite +def young_person(draw): + return dict( + age=draw(st.integers(16, 19)), + is_male=draw(st.booleans()), + current_education=draw(st.sampled_from(EDUCATION)), + age_started_or_accepted_current_education_or_training=draw(st.integers(14, 19)), + is_before_universal_credit_qualifying_young_person_terminal_date=draw( + st.booleans() + ), + is_looked_after_by_local_authority=draw(st.booleans()), + receives_benefits_in_own_right=draw(st.booleans()), + ) + + +@st.composite +def scenario(draw): + population = draw(st.lists(household(), min_size=1, max_size=4)) + h = draw(st.integers(0, len(population) - 1)) + f = draw(st.integers(0, len(population[h]) - 1)) + person = draw(young_person()) + # On some draws the person added to the head's family heads the household + # in place of its first adult, whose claimant role does not change. + if f == 0: + person["is_household_head"] = draw(st.booleans()) + return population, (h, f), person + + +def build(population, added=None, supply_roles=True): + """One situation for the population, optionally with a person added to + family ``added[0]`` = (household, family) with attributes ``added[1]``. + + Returns the situation and, per benefit unit, its (household, family). + An added person with ``is_household_head`` heads the household in place + of the head family's first adult.""" + people, benunits, homes, keys = {}, {}, {}, [] + for h, families in enumerate(population): + members = [] + for f, fam in enumerate(families): + name = f"h{h}_f{f}" + ids = [] + for i, age in enumerate(fam["adults"]): + pid = f"{name}_adult_{i}" + people[pid] = {"age": age, "is_household_head": False} + if supply_roles: + people[pid]["is_claimant_or_partner"] = True + elif fam["children"]: + people[pid]["is_parent"] = True + ids.append(pid) + for i, (age, male) in enumerate(fam["children"]): + pid = f"{name}_child_{i}" + people[pid] = {"age": age, "is_male": male, "is_household_head": False} + if supply_roles: + people[pid]["is_claimant_or_partner"] = False + ids.append(pid) + if added is not None and added[0] == (h, f): + pid = f"{name}_added" + people[pid] = { + **added[1], + "is_household_head": added[1].get("is_household_head", False), + } + if supply_roles: + people[pid]["is_claimant_or_partner"] = False + ids.append(pid) + extra = {} + if fam["role"] == "sharer": + extra["liable_for_share_of_household_rent"] = True + if fam["role"] in ("boarder", "lodger"): + people[ids[0]][f"rent_paid_as_{fam['role']}"] = 5_000 + added_head = ( + added is not None + and added[0] == (h, f) + and added[1].get("is_household_head", False) + ) + if fam["role"] == "head" and not added_head: + people[ids[0]]["is_household_head"] = True + benunits[name] = {"members": ids, **extra} + members.extend(ids) + keys.append((h, f)) + homes[f"h{h}"] = { + "members": members, + "rent": 12_000, + "tenure_type": "RENT_PRIVATELY", + "brma": "MAIDSTONE", + } + situation = {"people": people, "benunits": benunits, "households": homes} + return situation, keys + + +def bedrooms(situation): + sim = Simulation(situation=situation) + uc = np.asarray(sim.calculate("LHA_allowed_bedrooms", YEAR)) + hb = np.asarray(sim.calculate("housing_benefit_LHA_allowed_bedrooms", YEAR)) + return sim, uc, hb + + +def claim_roles(sim): + """Per benefit unit: whether it counts the household's non-dependants + for Universal Credit, and whether it is liable for the household's + rent.""" + counted = np.asarray(sim.calculate("uc_non_dependants_counted", YEAR)) + liable = ( + np.asarray( + sim.calculate("is_liable_for_household_rent", YEAR, map_to="benunit") + ) + > 0 + ) + return counted.astype(bool), liable + + +def own_claims(sim): + """Per benefit unit: whether it makes its own Universal Credit claim as a + renter, the test uc_non_dep_deductions applies to an in-unit + non-dependant's family.""" + return ( + np.asarray(sim.calculate("benunit_is_rent_liable", YEAR)) + & np.asarray(sim.calculate("is_uc_eligible", YEAR)) + & np.asarray(sim.calculate("claims_universal_credit", YEAR)) + ) + + +def has_own_uc_bedroom(sim, situation): + """Each person aged 16 or over who is not the claimant or partner: whether + they have a Universal Credit bedroom of their own. A qualifying young + person the renter is responsible for has one (para 10(1)(b)); one no one + is responsible for has none (para 9(2)(g)). Anyone else has one as a + non-dependant (para 10(1)(c)), unless they head the household, whom the + formula treats as liable for the rent (para 9(2)(d)).""" + qualifying = np.asarray( + sim.calculate("is_qualifying_young_person_for_universal_credit", YEAR) + ) + responsible = np.asarray( + sim.calculate("is_child_or_qualifying_young_person_for_universal_credit", YEAR) + ) + head = np.asarray(sim.calculate("is_resolved_household_head", YEAR)) + return dict(zip(situation["people"], responsible | (~qualifying & ~head))) + + +def fewest_rooms_for_children(children): + """Brute force: the fewest rooms holding the children two to a room, + where a pair must be of the same sex or both under 10.""" + + def can_share(a, b): + return a[1] == b[1] or (a[0] < 10 and b[0] < 10) + + def best(remaining): + if not remaining: + return 0 + first, rest = remaining[0], remaining[1:] + rooms = 1 + best(rest) + for j, other in enumerate(rest): + if can_share(first, other): + rooms = min(rooms, 1 + best(rest[:j] + rest[j + 1 :])) + return rooms + + return best(list(children)) + + +@PROPERTY_SETTINGS +@given(scenario(), st.booleans()) +def test_adding_a_person_aged_16_to_19_never_lowers_bedrooms(case, supply_roles): + population, target, person = case + before, keys = build(population, supply_roles=supply_roles) + after, keys_after = build(population, (target, person), supply_roles) + assert keys == keys_after + _, uc_before, hb_before = bedrooms(before) + _, uc_after, hb_after = bedrooms(after) + # 1. Monotonicity. + assert np.all(uc_after >= uc_before) + assert np.all(hb_after >= hb_before) + + +@PROPERTY_SETTINGS +@given(scenario()) +# A qualifying young person a local authority looks after, added to the +# head's family: no Universal Credit bedroom (para 9(2)(g)). +@example( + ( + [[dict(role="head", adults=[40], children=[])]], + (0, 0), + dict( + age=17, + is_male=True, + current_education="UPPER_SECONDARY", + age_started_or_accepted_current_education_or_training=16, + is_before_universal_credit_qualifying_young_person_terminal_date=True, + is_looked_after_by_local_authority=True, + receives_benefits_in_own_right=False, + ), + ) +) +# The same young person in a non-dependant's family: no Universal Credit +# bedroom in the renter's claim either (para 9(2)(g)). +@example( + ( + [ + [ + dict(role="head", adults=[40], children=[]), + dict(role="non_dependant", adults=[45], children=[]), + ] + ], + (0, 1), + dict( + age=17, + is_male=True, + current_education="UPPER_SECONDARY", + age_started_or_accepted_current_education_or_training=16, + is_before_universal_credit_qualifying_young_person_terminal_date=True, + is_looked_after_by_local_authority=True, + receives_benefits_in_own_right=False, + ), + ) +) +def test_exact_effect_of_adding_a_person_aged_16_to_19(case): + population, target, person = case + before, keys = build(population) + after, _ = build(population, (target, person)) + sim_before, uc_before, hb_before = bedrooms(before) + sim, uc_after, hb_after = bedrooms(after) + counted, liable = claim_roles(sim) + # Adding a dependant changes neither who counts the non-dependants nor + # who is liable for the rent. + counted_before, liable_before = claim_roles(sim_before) + assert np.array_equal(counted, counted_before) + assert np.array_equal(liable, liable_before) + gains = has_own_uc_bedroom(sim, after)[f"h{target[0]}_f{target[1]}_added"] + names = list(after["people"]) + marked = in_unit_non_dependants(sim)[ + names.index(f"h{target[0]}_f{target[1]}_added") + ] + # 2. Exact effect. + expected_uc = np.zeros(len(keys)) + expected_hb = np.zeros(len(keys)) + t = keys.index(target) + expected_uc[t] += 1 if gains else 0 + expected_hb[t] += 1 + role = population[target[0]][target[1]]["role"] + same_household = np.array([h == target[0] for h, _ in keys]) + passed_on = ( + role != "non_dependant" and marked and not own_claims(sim)[t] and not counted[t] + ) + if (role == "non_dependant" and gains) or passed_on: + expected_uc[same_household & counted] += 1 + if role in ("non_dependant", "boarder", "lodger"): + expected_hb[same_household & liable] += 1 + assert np.array_equal(uc_after - uc_before, expected_uc) + assert np.array_equal(hb_after - hb_before, expected_hb) + + +@PROPERTY_SETTINGS +@given(scenario()) +def test_bedrooms_match_an_independent_count_of_the_size_criteria(case): + population, target, person = case + situation, keys = build(population, (target, person)) + sim, uc, hb = bedrooms(situation) + counted, liable = claim_roles(sim) + gains = has_own_uc_bedroom(sim, situation) + own_claim = own_claims(sim) + names = list(situation["people"]) + marked = in_unit_non_dependants(sim) + occupier_roles = ("non_dependant", "boarder", "lodger") + # 3. Reference count. + for b, (h, f) in enumerate(keys): + families = population[h] + fam = families[f] + added = (h, f) == target + own_uc = int(added and gains[f"h{h}_f{f}_added"]) + own_hb = int(added) + outside_uc = outside_hb = 0 + hb_children = list(fam["children"]) + for g, other in enumerate(families): + if g == f: + continue + joins = target == (h, g) + if counted[b] and other["role"] == "non_dependant": + # UC: each non-dependant who is not a child (adults are + # supplied as claimant or partner). + outside_uc += len(other["adults"]) + outside_uc += int(joins and gains[f"h{h}_f{g}_added"]) + elif counted[b] and joins and not own_claim[keys.index((h, g))]: + # UC: an in-unit non-dependant of a family that makes no + # claim of its own counts in this claim (para 9(2)(f)). + outside_uc += int(marked[names.index(f"h{h}_f{g}_added")]) + if liable[b] and other["role"] in occupier_roles: + # HB: the occupier family's couple ((a)) or single adult + # ((b)), the added person ((b)) and its children. + outside_hb += 1 + int(joins) + hb_children += other["children"] + uc_children = fewest_rooms_for_children(fam["children"]) + assert uc[b] == 1 + own_uc + outside_uc + uc_children + assert hb[b] == 1 + own_hb + outside_hb + fewest_rooms_for_children(hb_children) + + +def in_unit_non_dependants(sim): + """Each person: aged 16 or over, neither the claimant nor partner, not a + Universal Credit qualifying young person, and not the household head + (UC Regs 2013 Sch 4 para 9(2), with (d) for the head, the householder + in whose name the accommodation is rented). Checks that the model's + is_benefit_unit_non_dependant_for_universal_credit marks the same + people.""" + age = np.asarray(sim.calculate("age", YEAR)) + claimant_or_partner = np.asarray(sim.calculate("is_claimant_or_partner", YEAR)) + qualifying = np.asarray( + sim.calculate("is_qualifying_young_person_for_universal_credit", YEAR) + ) + household_head = np.asarray(sim.calculate("is_resolved_household_head", YEAR)) + marked = (age >= 16) & ~claimant_or_partner & ~qualifying & ~household_head + model = np.asarray( + sim.calculate("is_benefit_unit_non_dependant_for_universal_credit", YEAR) + ) + assert np.array_equal(marked, model) + return marked + + +@PROPERTY_SETTINGS +@given(scenario()) +def test_every_in_unit_non_dependant_has_a_bedroom(case): + population, target, person = case + situation, keys = build(population, (target, person)) + sim, uc, _ = bedrooms(situation) + marked = in_unit_non_dependants(sim) + names = list(situation["people"]) + # 4. A marked member has a bedroom of their own: without them, their + # family has one fewer, and so does the claim counting the household's + # non-dependants if they are in a non-dependant's family, or in a family + # that neither makes its own claim nor is that claim. Only the added + # person can be marked: the generated adults are claimants or partners + # and the generated children are under 16. + if marked[names.index(f"h{target[0]}_f{target[1]}_added")]: + without, _ = build(population) + _, uc_without, _ = bedrooms(without) + expected = np.zeros(len(keys)) + expected[keys.index(target)] = 1 + role = population[target[0]][target[1]]["role"] + counted, _ = claim_roles(sim) + t = keys.index(target) + if role == "non_dependant" or not (own_claims(sim)[t] or counted[t]): + same_household = np.array([h == target[0] for h, _ in keys]) + expected[same_household & counted] += 1 + assert np.array_equal(uc - uc_without, expected) + # Making every marked member a qualifying young person the renter is + # responsible for leaves every family's bedrooms unchanged, except that + # the claim counting the household's non-dependants loses the bedroom of + # one whose own family makes no claim and is not that claim: another + # family's qualifying young person is not that renter's non-dependant + # (para 9(2)(g)). + expected_change = np.zeros(len(keys)) + if marked[names.index(f"h{target[0]}_f{target[1]}_added")]: + counted, _ = claim_roles(sim) + t = keys.index(target) + role = population[target[0]][target[1]]["role"] + if role != "non_dependant" and not (own_claims(sim)[t] or counted[t]): + same_household = np.array([h == target[0] for h, _ in keys]) + expected_change[same_household & counted] = -1 + as_dependants = { + **situation, + "people": { + name: ( + { + **attributes, + "is_qualifying_young_person_for_universal_credit": True, + "is_looked_after_by_local_authority": False, + } + if marked[i] + else attributes + ) + for i, (name, attributes) in enumerate(situation["people"].items()) + }, + } + sim_as_dependants, uc_as_dependants, _ = bedrooms(as_dependants) + assert not in_unit_non_dependants(sim_as_dependants).any() + assert np.array_equal(uc_as_dependants - uc, expected_change) + + +@st.composite +def adult_member(draw): + """A member aged 16 or over who is not in education, so never a + qualifying young person, with or without Attendance Allowance (an + exemption under UC Regs 2013 Sch 4 para 16(2)).""" + return dict( + age=draw(st.integers(16, 64)), + is_male=draw(st.booleans()), + current_education="NOT_IN_EDUCATION", + is_looked_after_by_local_authority=False, + attendance_allowance=draw(st.sampled_from([0, 5_000])), + ) + + +@st.composite +def adult_scenario(draw): + population = draw(st.lists(household(), min_size=1, max_size=3)) + h = draw(st.integers(0, len(population) - 1)) + f = draw(st.integers(0, len(population[h]) - 1)) + # Whether each family would claim Universal Credit, so that the claim + # bearing a non-dependant is sometimes not their own family's. + would_claim = [[draw(st.booleans()) for _ in families] for families in population] + return population, (h, f), draw(adult_member()), would_claim + + +def with_claim_flags(situation, would_claim): + for name, unit in situation["benunits"].items(): + if name.startswith("h") and "_f" in name: + h, f = name[1:].split("_f") + unit["would_claim_uc"] = would_claim[int(h)][int(f)] + return situation + + +def in_own_benefit_unit(population, target, person, would_claim): + """The population with ``person`` in a benefit unit of their own, in the + household of family ``target``, not claiming Universal Credit.""" + situation, keys = build(population) + situation = with_claim_flags(situation, would_claim) + h = target[0] + situation["people"]["moved"] = { + **person, + "is_household_head": False, + "is_claimant_or_partner": True, + } + situation["benunits"]["moved"] = {"members": ["moved"], "would_claim_uc": False} + situation["households"][f"h{h}"]["members"].append("moved") + return situation, keys + + +def claim_and_contributions(situation): + sim = Simulation(situation=situation) + + def calc(variable): + return np.asarray(sim.calculate(variable, YEAR)) + + own_claim = ( + calc("benunit_is_rent_liable") + & calc("is_uc_eligible") + & calc("claims_universal_credit") + ) + return sim, calc, own_claim + + +@PROPERTY_SETTINGS +@given(adult_scenario()) +def test_every_in_unit_non_dependant_with_a_room_owes_a_contribution(case): + population, target, person, would_claim = case + before, keys = build(population) + after, _ = build(population, (target, person)) + before = with_claim_flags(before, would_claim) + after = with_claim_flags(after, would_claim) + sim_before, calc_before, own_claim_before = claim_and_contributions(before) + sim, calc, own_claim = claim_and_contributions(after) + counted = calc("uc_non_dependants_counted") + assert np.array_equal(own_claim, own_claim_before) + assert np.array_equal(counted, calc_before("uc_non_dependants_counted")) + # 5. Nobody owes a contribution without being a non-dependant (Sch 4 + # para 9(2) and para 13). + owes = calc("uc_individual_non_dep_deduction") > 0 + non_dependant = calc("is_benefit_unit_non_dependant_for_universal_credit") | calc( + "is_non_dependant_of_household_head" + ) + assert not np.any(owes & ~non_dependant) + names = list(after["people"]) + i = names.index(f"h{target[0]}_f{target[1]}_added") + in_unit = calc("is_benefit_unit_non_dependant_for_universal_credit")[i] + head = calc("is_resolved_household_head")[i] + # The added member is never a qualifying young person or a claimant, so + # the definition reduces to the inputs. + assert in_unit == (person["age"] >= 16 and not head) + if not in_unit: + return + # An in-unit non-dependant owes a contribution unless aged under 21 or + # receiving Attendance Allowance (para 16(2)(a), (2)(c)). + p = sim.tax_benefit_system.parameters( + YEAR + ).gov.dwp.universal_credit.elements.housing.non_dep_deduction + exempt = person["age"] < p.age_threshold or person["attendance_allowance"] > 0 + assert owes[i] == (not exempt) + # The claim that bears them: their own family's if it makes one, + # otherwise the one counting the household's non-dependants. + b = keys.index(target) + role = population[target[0]][target[1]]["role"] + same_household = np.array([h == target[0] for h, _ in keys]) + if own_claim[b] and role != "non_dependant": + bearer = np.arange(len(keys)) == b + else: + bearer = same_household & counted.astype(bool) + renter_exempt = calc("uc_non_dep_deductions_renter_exempt") + expected = bearer * (not exempt) * ~renter_exempt * p.amount * 12 + delta = calc("uc_non_dep_deductions") - calc_before("uc_non_dep_deductions") + assert np.allclose(delta, expected) + # The claim that bears the contribution also has their bedroom. + rooms = calc("LHA_allowed_bedrooms") - calc_before("LHA_allowed_bedrooms") + assert np.all(rooms[bearer] == 1) + + +@PROPERTY_SETTINGS +@given(adult_scenario()) +def test_moving_a_non_claiming_adult_out_of_the_benefit_unit_changes_no_claim(case): + population, target, person, would_claim = case + inside, keys = build(population, (target, person)) + inside = with_claim_flags(inside, would_claim) + outside, _ = in_own_benefit_unit(population, target, person, would_claim) + _, calc_in, own_claim_in = claim_and_contributions(inside) + _, calc_out, own_claim_out = claim_and_contributions(outside) + n = len(keys) + # The household head's family keeps its claim either way. + assert np.array_equal(own_claim_in, own_claim_out[:n]) + # 6. Every family making a claim keeps its bedrooms, contribution and + # award whether the adult is coded in a benefit unit or in their own. + for variable in ( + "LHA_allowed_bedrooms", + "uc_non_dep_deductions", + "universal_credit", + ): + a = calc_in(variable)[own_claim_in] + b = calc_out(variable)[:n][own_claim_in] + assert np.allclose(a, b), variable diff --git a/policyengine_uk/tests/test_non_dependant_cycle_contracts.py b/policyengine_uk/tests/test_non_dependant_cycle_contracts.py new file mode 100644 index 0000000000..7be5feccd5 --- /dev/null +++ b/policyengine_uk/tests/test_non_dependant_cycle_contracts.py @@ -0,0 +1,549 @@ +"""Universal Credit and Housing Benefit when the inputs supply cap totals. + +Housing Benefit's non-dependant deductions read universal_credit, so no +baseline formula on Universal Credit's route may calculate Housing Benefit. +The Universal Credit cap counts Housing Benefit as nil (UC Regs 2013 reg +80(2A)): its basket, uc_benefit_cap_welfare_benefits, adds up the other +welfare benefits itself. Inputs that supply benefit_cap_welfare_benefits, a +total that includes Housing Benefit, also supply housing_benefit_pre_benefit_cap, +which the basket subtracts, or supply the basket itself. + +The contract holds for every population: either the inputs leave +benefit_cap_welfare_benefits to the model, or they supply +housing_benefit_pre_benefit_cap or uc_benefit_cap_welfare_benefits beside it; +otherwise the basket raises ValueError, whether or not any household holds a +non-dependant. A value a scenario stores as the model's own (set_model_value) +supplies neither input. Under the contract, each household's Universal Credit +and Housing Benefit equal its results alone. A reform that makes Universal +Credit read calculated Housing Benefit adds a cycle, and the engine raises +CycleError. + +The tests use two independent households: a single Universal Credit +claimant with £20,000 before a £14,753 cap and no rent; and a 70-year-old +paying £6,000 rent with a 40-year-old non-dependant who earns £9,000 at 20 +hours and claims no Universal Credit. Alone, the first gets £14,753 of +Universal Credit. For the second's non-dependant the council deducts +£19.65 x 52 = £1,021.80 (HB Regs 2006 regs 70 and 74(2)(a), 2025-26), +leaving £4,978.20 of Housing Benefit. +""" + +import copy + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st +from policyengine_core.errors import CycleError +from policyengine_core.reforms import Reform +from policyengine_core.simulations import Simulation + +from policyengine_uk import CountryTaxBenefitSystem +from policyengine_uk.utils.supplied_inputs import set_model_value +from policyengine_uk.variables.gov.dwp.benefit_cap_welfare_benefits import ( + benefit_cap_welfare_benefits as CombinedTotal, +) +from policyengine_uk.variables.gov.dwp.universal_credit.benefit_cap.uc_benefit_cap_welfare_benefits import ( + uc_benefit_cap_welfare_benefits as Basket, +) +from policyengine_uk.variables.gov.dwp.universal_credit.universal_credit_pre_benefit_cap import ( + universal_credit_pre_benefit_cap as PreCap, +) + +YEAR = 2025 +UC_MIXED = [14_753.0, 0.0, 0.0] +HB_MIXED = [0.0, 4_978.20, 0.0] +FIRST_REQUESTS = [ + "universal_credit", + "housing_benefit", + "housing_benefit_non_dep_deductions", +] +NO_AWARDS = dict( + child_benefit=0.0, + child_tax_credit=0.0, + working_tax_credit=0.0, + income_support=0.0, + jsa_income=0.0, + esa_income=0.0, + pension_credit=0.0, + uc_deductions=0.0, + uc_deduction_rate=0.0, + uc_childcare_element=0.0, +) + + +@pytest.fixture(scope="module") +def system(): + return CountryTaxBenefitSystem() + + +def situation(which="mixed", year=YEAR, full_uc=False): + """The claimant's household ("single"), the non-dependant's + ("nondep"), or both ("mixed"), with no cap total supplied.""" + people, units, households = {}, {}, {} + if which in ("single", "mixed"): + people["uc"] = dict( + age=40, + current_education="NOT_IN_EDUCATION", + employment_income=1_000.0, + weekly_hours=20.0, + ) + units["uc"] = dict( + members=["uc"], + **NO_AWARDS, + would_claim_uc=True, + uc_benefit_cap=14_753.0, + benunit_is_rent_liable=False, + ) + if not full_uc: + units["uc"]["universal_credit_pre_benefit_cap"] = 20_000.0 + households["single"] = dict( + members=["uc"], + country="ENGLAND", + local_authority="MAIDSTONE", + rent=0.0, + tenure_type="RENT_PRIVATELY", + ) + if which in ("nondep", "mixed"): + people["applicant"] = dict(age=70, current_education="NOT_IN_EDUCATION") + people["nondep"] = dict( + age=40, + current_education="NOT_IN_EDUCATION", + employment_income=9_000.0, + weekly_hours=20.0, + ) + for name in ["applicant", "nondep"]: + units[name] = dict( + members=[name], + **NO_AWARDS, + would_claim_uc=False, + uc_benefit_cap=14_753.0, + benunit_is_rent_liable=name == "applicant", + ) + if not full_uc: + units[name]["universal_credit_pre_benefit_cap"] = 0.0 + households["nondep"] = dict( + members=["applicant", "nondep"], + country="ENGLAND", + local_authority="MAIDSTONE", + rent=6_000.0, + tenure_type="RENT_PRIVATELY", + ) + return keyed(dict(people=people, benunits=units, households=households), year) + + +def keyed(data, year): + """Key every input by year.""" + for entities in data.values(): + for entity in entities.values(): + for name, value in entity.items(): + if name != "members" and not isinstance(value, dict): + entity[name] = {year: value} + return data + + +def supply(data, variable, values, year=YEAR): + """Supply ``variable`` for every benefit unit, from ``values`` by unit + name (nil for a unit it leaves out).""" + for name, unit in data["benunits"].items(): + unit[variable] = {year: float(values.get(name, 0.0))} + return data + + +def make(system, data, year=YEAR, trace=False): + return Simulation( + tax_benefit_system=system, + situation=copy.deepcopy(data), + default_input_period=year, + default_calculation_period=year, + trace=trace, + ) + + +def with_totals(which, **inputs): + """The population with the combined total its Universal Credit and + Housing Benefit imply supplied, and any other inputs.""" + data = situation(which) + supply(data, "benefit_cap_welfare_benefits", {"uc": 20_000.0}) + for variable, values in inputs.items(): + supply(data, variable, values) + return data + + +def traced_names(simulation): + return {key.split("<")[0] for key in simulation.tracer.get_serialized_flat_trace()} + + +@pytest.mark.parametrize("first", FIRST_REQUESTS) +def test_a_supplied_universal_credit_basket_keeps_households_independent(system, first): + """With the combined total and Universal Credit's basket supplied, each + household gets its results alone, whatever the first request.""" + basket = {"uc": 20_000.0} + alone = [ + make(system, with_totals(which, uc_benefit_cap_welfare_benefits=basket)) + for which in ["single", "nondep"] + ] + mixed = make(system, with_totals("mixed", uc_benefit_cap_welfare_benefits=basket)) + mixed.calculate(first, YEAR) + for name, expected in [ + ("universal_credit", UC_MIXED), + ("housing_benefit", HB_MIXED), + ]: + result = mixed.calculate(name, YEAR) + np.testing.assert_allclose(result, expected, atol=0.01, err_msg=name) + np.testing.assert_allclose( + result, + np.concatenate([sim.calculate(name, YEAR) for sim in alone]), + atol=0.01, + err_msg=name, + ) + np.testing.assert_allclose( + mixed.calculate("housing_benefit_non_dep_deductions", YEAR), + [0.0, 1_021.80, 0.0], + atol=0.01, + ) + + +@pytest.mark.parametrize("first", FIRST_REQUESTS) +def test_a_supplied_housing_benefit_gives_its_arithmetic(system, first): + """With the combined total and Housing Benefit supplied, the basket is + their difference, and Universal Credit and Housing Benefit follow from + it. The claimant's total of £26,000 includes £6,000 of Housing Benefit + (as in specified accommodation): the basket counts £20,000, £5,247 over + the cap, so the award comes to £14,753, and HB reg 75F(1)(g) leaves the + Housing Benefit uncapped. The applicant's total is their £4,978.20 of + Housing Benefit, and pension age puts them outside the cap.""" + data = situation("mixed") + supply( + data, + "benefit_cap_welfare_benefits", + {"uc": 26_000.0, "applicant": 4_978.20}, + ) + supply( + data, + "housing_benefit_pre_benefit_cap", + {"uc": 6_000.0, "applicant": 4_978.20}, + ) + sim = make(system, data) + sim.calculate(first, YEAR) + np.testing.assert_allclose( + sim.calculate("uc_benefit_cap_welfare_benefits", YEAR), + [20_000.0, 0.0, 0.0], + atol=0.01, + ) + np.testing.assert_allclose( + sim.calculate("universal_credit", YEAR), UC_MIXED, atol=0.01 + ) + np.testing.assert_allclose( + sim.calculate("housing_benefit", YEAR), [6_000.0, 4_978.20, 0.0], atol=0.01 + ) + # The deduction still reads the non-dependant's wages and Universal + # Credit; the supplied Housing Benefit does not read the deduction. + np.testing.assert_allclose( + sim.calculate("housing_benefit_non_dep_deductions", YEAR), + [0.0, 1_021.80, 0.0], + atol=0.01, + ) + + +@pytest.mark.parametrize("which", ["mixed", "single"]) +def test_a_supplied_total_alone_raises(system, which): + """A supplied combined total without housing_benefit_pre_benefit_cap or + uc_benefit_cap_welfare_benefits raises ValueError, with or without a + non-dependant in the population, and the message names both inputs.""" + sim = make(system, with_totals(which)) + with pytest.raises(ValueError) as error: + sim.calculate("universal_credit", YEAR) + message = str(error.value) + assert "housing_benefit_pre_benefit_cap" in message + assert "uc_benefit_cap_welfare_benefits" in message + assert "benefit_cap_welfare_benefits" in message + + +def test_a_reformed_total_leaves_universal_credit_on_its_basket(system): + """A reform that gives benefit_cap_welfare_benefits a new formula changes + the Housing Benefit cap's total, and Universal Credit's basket still adds + up the benefits itself.""" + + def formula(benunit, period, parameters): + return CombinedTotal.formula(benunit, period, parameters) + 1_000 + + class ReformedTotal(Reform): + def apply(self): + self.update_variable( + type( + "benefit_cap_welfare_benefits", + (CombinedTotal,), + dict(formula=formula), + ) + ) + + baseline = make(system, situation("mixed")) + reformed = make(ReformedTotal(system), situation("mixed")) + np.testing.assert_allclose( + reformed.calculate("benefit_cap_welfare_benefits", YEAR), + baseline.calculate("benefit_cap_welfare_benefits", YEAR) + 1_000, + atol=0.01, + ) + for name in ["uc_benefit_cap_welfare_benefits", "universal_credit"]: + np.testing.assert_allclose( + reformed.calculate(name, YEAR), + baseline.calculate(name, YEAR), + atol=0.01, + err_msg=name, + ) + np.testing.assert_allclose( + reformed.calculate("universal_credit", YEAR), UC_MIXED, atol=0.01 + ) + + +def test_model_values_supply_neither_input(system): + """set_model_value stores a value as the model's own, so it supplies + neither the combined total nor Housing Benefit. A model value of the + total leaves Universal Credit's basket adding up the benefits itself; a + supplied total with a model value of Housing Benefit still raises.""" + baseline = make(system, situation("mixed")) + basket = baseline.calculate("uc_benefit_cap_welfare_benefits", YEAR) + stored_total = make(system, situation("mixed")) + set_model_value( + stored_total, + "benefit_cap_welfare_benefits", + YEAR, + np.array([30_000.0, 0.0, 0.0]), + ) + np.testing.assert_allclose( + stored_total.calculate("uc_benefit_cap_welfare_benefits", YEAR), + basket, + atol=0.01, + ) + np.testing.assert_allclose( + stored_total.calculate("universal_credit", YEAR), UC_MIXED, atol=0.01 + ) + stored_housing_benefit = make(system, with_totals("mixed")) + set_model_value( + stored_housing_benefit, + "housing_benefit_pre_benefit_cap", + YEAR, + np.array([0.0, 4_978.20, 0.0]), + ) + with pytest.raises(ValueError, match="housing_benefit_pre_benefit_cap"): + stored_housing_benefit.calculate("universal_credit", YEAR) + + +UC_HOUSEHOLD = st.fixed_dictionaries( + dict( + kind=st.just("uc"), + universal_credit_pre_benefit_cap=st.floats(0, 40_000), + uc_benefit_cap=st.sampled_from([14_753.0, 22_020.0]), + child_benefit=st.sampled_from([0.0, 1_500.0]), + employment_income=st.floats(0, 20_000), + ) +) +NON_DEPENDANT_HOUSEHOLD = st.fixed_dictionaries( + dict( + kind=st.just("nondep"), + rent=st.sampled_from([3_000.0, 6_000.0]), + employment_income=st.floats(0, 30_000), + weekly_hours=st.sampled_from([0.0, 20.0, 35.0]), + universal_credit_pre_benefit_cap=st.sampled_from([0.0, 5_000.0, 15_000.0]), + ) +) + + +def generated_population(households): + """Each generated household, with its Universal Credit basket and the + combined total it implies supplied for every benefit unit.""" + people, units, homes, basket = {}, {}, {}, {} + for h, household in enumerate(households): + if household["kind"] == "uc": + claimant = f"uc_{h}" + people[claimant] = dict( + age=40, + current_education="NOT_IN_EDUCATION", + employment_income=household["employment_income"], + weekly_hours=20.0, + ) + units[claimant] = dict( + members=[claimant], + **{**NO_AWARDS, "child_benefit": household["child_benefit"]}, + would_claim_uc=True, + uc_benefit_cap=household["uc_benefit_cap"], + benunit_is_rent_liable=False, + universal_credit_pre_benefit_cap=household[ + "universal_credit_pre_benefit_cap" + ], + ) + basket[claimant] = ( + household["universal_credit_pre_benefit_cap"] + + household["child_benefit"] + ) + homes[f"home_{h}"] = dict( + members=[claimant], + country="ENGLAND", + local_authority="MAIDSTONE", + rent=0.0, + tenure_type="RENT_PRIVATELY", + ) + else: + applicant, non_dependant = f"applicant_{h}", f"nondep_{h}" + on_uc = household["universal_credit_pre_benefit_cap"] > 0 + people[applicant] = dict(age=70, current_education="NOT_IN_EDUCATION") + people[non_dependant] = dict( + age=40, + current_education="NOT_IN_EDUCATION", + employment_income=household["employment_income"], + weekly_hours=household["weekly_hours"], + ) + units[applicant] = dict( + members=[applicant], + **NO_AWARDS, + would_claim_uc=False, + uc_benefit_cap=14_753.0, + benunit_is_rent_liable=True, + universal_credit_pre_benefit_cap=0.0, + ) + units[non_dependant] = dict( + members=[non_dependant], + **NO_AWARDS, + would_claim_uc=on_uc, + uc_benefit_cap=14_753.0, + benunit_is_rent_liable=False, + universal_credit_pre_benefit_cap=household[ + "universal_credit_pre_benefit_cap" + ], + ) + basket[non_dependant] = household["universal_credit_pre_benefit_cap"] + homes[f"home_{h}"] = dict( + members=[applicant, non_dependant], + country="ENGLAND", + local_authority="MAIDSTONE", + rent=household["rent"], + tenure_type="RENT_PRIVATELY", + ) + data = keyed(dict(people=people, benunits=units, households=homes), YEAR) + # The supplied units hold no Housing Benefit in the cap: the claimants + # pay no rent, and the applicants have reached pension age. + supply(data, "uc_benefit_cap_welfare_benefits", basket) + supply(data, "benefit_cap_welfare_benefits", basket) + return data + + +@settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given( + households=st.lists( + st.one_of(UC_HOUSEHOLD, NON_DEPENDANT_HOUSEHOLD), min_size=1, max_size=3 + ) +) +def test_each_household_gets_its_results_alone(system, households): + """For any mix of independent households, with Universal Credit's basket + supplied, each household's Universal Credit, Housing Benefit and + non-dependant deductions equal its results alone.""" + mixed = make(system, generated_population(households)) + alone = [ + make(system, generated_population([household])) for household in households + ] + for name in [ + "universal_credit", + "housing_benefit", + "housing_benefit_non_dep_deductions", + ]: + np.testing.assert_allclose( + mixed.calculate(name, YEAR), + np.concatenate([sim.calculate(name, YEAR) for sim in alone]), + atol=0.01, + err_msg=name, + ) + + +def test_computed_universal_credit_and_local_schemes(system): + """With nothing supplied, Universal Credit's traced calculation reads no + Housing Benefit variable but the reported award, and the national and + local Council Tax Reduction schemes then read Universal Credit.""" + # Each local scheme's parameters start in 2026-27. + year = 2026 + template = situation("mixed", year=year, full_uc=True) + template["people"]["uc"]["housing_benefit_reported"] = {year: 6_000.0} + data = {group: {} for group in template} + for authority in [ + "MERTON", + "KINGSTON_UPON_THAMES", + "NEWHAM", + "WESTMINSTER", + "OXFORD", + ]: + prefix = authority.lower() + "_" + for group, entities in template.items(): + for name, entity in entities.items(): + copied = copy.deepcopy(entity) + if "members" in copied: + copied["members"] = [ + prefix + member for member in copied["members"] + ] + if group == "benunits": + # A reported Housing Benefit award otherwise makes + # claims_all_entitled_benefits false, so this family + # would not claim CTR by default. + copied["would_claim_council_tax_reduction"] = {year: True} + if group == "households": + copied["local_authority"] = {year: authority} + copied["council_tax"] = {year: 2_000.0} + data[group][prefix + name] = copied + sim = make(system, data, year=year, trace=True) + universal_credit = sim.calculate("universal_credit", year) + names = traced_names(sim) + assert universal_credit[0] > 0 + assert "uc_maximum_amount" in names + assert "housing_benefit_reported" in names + assert not { + name + for name in names + if name.startswith("housing_benefit") and name != "housing_benefit_reported" + } + for name in [ + "housing_benefit", + "council_tax_reduction", + "merton_council_tax_reduction", + "kingston_upon_thames_council_tax_reduction", + "newham_council_tax_reduction", + "westminster_council_tax_reduction", + "oxford_council_tax_reduction", + ]: + values = sim.calculate(name, year) + if name.endswith("_council_tax_reduction"): + assert values.max() > 0, (name, values) + + +@pytest.mark.parametrize( + "variable,parent", + [ + ("uc_benefit_cap_welfare_benefits", Basket), + ("universal_credit_pre_benefit_cap", PreCap), + ], +) +def test_a_reform_reading_calculated_housing_benefit_raises_cycle_error( + system, variable, parent +): + """A reform that adds calculated Housing Benefit to Universal Credit's + basket or award makes Universal Credit and the deductions depend on each + other, and the engine raises CycleError.""" + + def formula(benunit, period, parameters): + return parent.formula(benunit, period, parameters) + benunit( + "housing_benefit", period + ) + + replacement = type(variable, (parent,), dict(formula=formula)) + + class CyclicReform(Reform): + def apply(self): + self.update_variable(replacement) + + data = situation("mixed") + for unit in data["benunits"].values(): + unit.pop(variable, None) + with pytest.raises(CycleError): + make(CyclicReform(system), data).calculate("housing_benefit", YEAR) diff --git a/policyengine_uk/tests/test_non_dependant_deduction_properties.py b/policyengine_uk/tests/test_non_dependant_deduction_properties.py new file mode 100644 index 0000000000..5a172b2bad --- /dev/null +++ b/policyengine_uk/tests/test_non_dependant_deduction_properties.py @@ -0,0 +1,1374 @@ +"""Property-based and exhaustive tests for the Housing Benefit and national +Council Tax Reduction non-dependant deductions. + +Law: +- Housing Benefit: SI 2006/213 reg 74 (working age) and SI 2006/214 reg 55 + (pension age). +- Council Tax Reduction: SI 2012/2885 Sch 1 para 8 (England, pensioners); + WSI 2013/3029 Sch 1 para 3 and Sch 6 para 5 (Wales); SSI 2012/319 reg 48 and + SSI 2021/249 reg 90 (Scotland). + +Invariants, for any generated population of households, each a claimant family +and a non-dependant family of one or two adults: + +1. An exempt non-dependant's individual deduction is 0, for Housing Benefit and + for Council Tax Reduction. +2. Bounds: for an eligible non-dependant who is not exempt, the council + deducts between the scale's lowest and highest weekly amounts. In English + working-age households (local schemes) the national CTR deduction is 0. +3. For a non-dependant outside remunerative work (under 16 hours or, for Housing + Benefit, on IS, JSA(IB) or ESA(IR): HB reg 6(6)), the council deducts the lowest + amount whatever their income. +4. If the claimant or partner is blind or receives a qualifying disability + benefit (read from the generated inputs, and checked against the model's + flags), the claimant family's deductions are 0. +5. Aggregation: a family's deductions are the sum, over the other families in + the household, of their claimant and partner's higher amount (both amounts + for a Welsh working-age applicant's non-dependant couple whose own + Universal Credit award assesses either member; another member's award as + a single claimant leaves the couple one deduction) plus each other + member's, recomputed here from the individual amounts, times the family's + share of the rent (Housing Benefit, reg 74(5)) or of the council tax + liability (CTR, Sch 1 para 8(5)). +6. Monotonicity (metamorphic): raising any non-dependant's income never lowers + an individual deduction or a family's deductions. +7. Exhaustive: in every year from 2015 (2019 for Housing Benefit) to 2030, + including uprated years, an income at a band's lower edge gets that band's + amount and the next lower representable income gets the band below's. +8. Differential: each CTR scale's parameter equals the enacted sums in + tests/fixtures/council_tax_reduction_non_dependant_deductions.yaml. +9. Differential: each exemption flag equals a reading of the provisions + straight from the generated inputs. Housing Benefit: a full-time student, + a State Pension Credit recipient, or under 25 and on Income Support, + income-based JSA or Universal Credit without earned income. CTR (any age): + a full-time student, on IS, JSA(IB), ESA(IR) or SPC, or, in a national + scheme, on Universal Credit without earned income, or an adult for whom + someone else is entitled to child benefit, which needs a claim (LGFA 1992 + Sch 1 para 3). In Scotland's working-age scheme from April 2022, also + anyone whose partner is on IS, JSA(IB) or ESA(IR) and who is not on + Universal Credit (SSI 2021/249 reg 90(8)(a), reg 4(1)). IS, JSA(IB), + ESA(IR) and SPC count only for the person they are payable to, read from + the model's is_on_* status (HB Regs 2006 reg 2(1), (3), (3A)); where the + claimant or partner reports the award, or no one does, that is the + reporter or the benefit unit's head, which the test checks. Universal + Credit counts for the claimants it assesses: the members the generated + is_uc_claimant flags name (by default the claimant and partner), the two + eldest where the inputs flag more, which the test checks. Earned income is + Universal Credit's own calculation (invariant 13). +10. (In test_rent_of_sharers_boarders_and_lodgers_properties.py.) +11. Banding: for a non-dependant in remunerative work who is neither exempt nor + on IS, JSA(IB) or ESA(IR), the council deducts the amount of the band whose lower + edge is the highest not above their normal gross weekly income, computed here + from the generated inputs: earnings, statutory sick, maternity and paternity + pay, self-employed profit with a loss as nil, and other investment income; + for the claimant or partner of the non-dependant's family, the couple's + sum plus the family's IS, JSA(IB), ESA(IR) and Pension Credit, and its + Universal Credit where the award assesses either of them. The council + bands another member who claims Universal Credit alone on their own + income and that award. The test skips incomes within a penny a week of an + edge, since the inputs hold float32 values. +12. The Universal Credit cap counts the Housing Benefit cap's welfare + benefits other than Housing Benefit (UC Regs 2013 reg 80(2A)), and no + baseline formula on universal_credit's route calculates Housing Benefit: + its traced calculation reads no Housing Benefit variable but the reported + award. Inputs that supply benefit_cap_welfare_benefits, a total that + includes Housing Benefit, also supply housing_benefit_pre_benefit_cap or + uc_benefit_cap_welfare_benefits, or the basket raises ValueError. A + non-dependant family's gross income counts its award after the cap and + before debt deductions: universal_credit plus uc_deductions, which equals + the award before the cap less the cap reduction applied, under current law + and under an 85% protected minimum floor; the floor never lowers an award. + Under a reform that scales universal_credit, each household's deductions + equal those of the same household with the reformed award supplied, and + the Housing Benefit deduction is the band of wages plus that award, so it + moves when the award crosses a band. + (test_non_dependant_cycle_contracts.py checks the supplied-total contract + over mixed populations, and that a reform making Universal Credit read + calculated Housing Benefit raises CycleError.) +13. Differential: the Universal Credit exemption's earned-income test agrees + with Universal Credit's own calculation (uc_individual_earned_income) + across employment expenses, trading losses, company income attributed to + its owner, pension contributions and the minimum income floor of single + claimants and couples; and it agrees with the inputs where they settle it + without the tax and National Insurance calculation. +""" + +from functools import lru_cache +from pathlib import Path + +import numpy as np +import pytest +import yaml +from hypothesis import HealthCheck, example, given, settings +from hypothesis import strategies as st + +from policyengine_uk import CountryTaxBenefitSystem, Simulation +from policyengine_uk.model_api import YEAR, BenUnit, Variable + +PROPERTY_SETTINGS = settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +# Local authorities without a modelled local scheme. +COUNTRIES = { + "ENGLAND": "MAIDSTONE", + "WALES": "CARDIFF", + "SCOTLAND": "GLASGOW_CITY", +} +CTR_SCALES = { + "ENGLAND": "england.council_tax_reduction.pensioners.non_dep_deduction", + "WALES": "wales.council_tax_reduction.non_dep_deduction", + "SCOTLAND": "scotland.council_tax_reduction.non_dep_deduction", +} +FIXTURE = ( + Path(__file__).parent + / "fixtures" + / "council_tax_reduction_non_dependant_deductions.yaml" +) +WEEKS = 52 +CLAIMANT_DISABILITY = [ + None, + None, + None, + "is_blind", + "attendance_allowance", + "dla_sc", + "pip_dl", + "armed_forces_independence_payment", +] +BENEFITS = [ + "income_support", + "jsa_income", + "esa_income", + "pension_credit", + "universal_credit_pre_benefit_cap", +] +REPORTED = { + "income_support": "income_support_reported", + "jsa_income": "jsa_income_reported", + "esa_income": "esa_income_reported", + "pension_credit": "pension_credit_reported", +} +IS_ON = { + "income_support": "is_on_income_support", + "jsa_income": "is_on_income_based_jsa", + "esa_income": "is_on_income_related_esa", + "pension_credit": "is_on_pension_credit", +} +STATUTORY_PAY = [ + "statutory_sick_pay", + "statutory_maternity_pay", + "statutory_paternity_pay", +] +# Family benefits counted in gross income, held at zero so the oracle knows +# each family's gross income. +OTHER_FAMILY_BENEFITS = ["child_tax_credit", "working_tax_credit", "child_benefit"] +SYSTEM = CountryTaxBenefitSystem() + + +def for_years(situation, years): + """Key every input by year: unkeyed inputs apply only to the default + period.""" + for group in situation.values(): + for entity in group.values(): + for key, value in entity.items(): + if key != "members" and not isinstance(value, dict): + entity[key] = {str(year): value for year in years} + return situation + + +@st.composite +def households(draw): + # The CTR applicant is the family with the household's oldest member, so + # claimants are older than their non-dependants. State Pension age is at + # most 67 in 2025-30. + claimant_age = draw(st.one_of(st.integers(50, 64), st.integers(68, 95))) + non_dependants = [ + dict( + age=draw(st.integers(18, claimant_age - 1)), + education=draw( + st.sampled_from( + ["NOT_IN_EDUCATION"] * 4 + ["TERTIARY", "UPPER_SECONDARY"] + ) + ), + earnings=draw(st.one_of(st.just(0.0), st.floats(0, 60_000))), + other_income=draw(st.one_of(st.just(0.0), st.floats(0, 30_000))), + self_employment=draw(st.one_of(st.just(0.0), st.floats(-20_000, 20_000))), + statutory_pay={ + name: draw(st.one_of(st.just(0.0), st.floats(0, 10_000))) + for name in STATUTORY_PAY + }, + pension_contributions=draw(st.one_of(st.just(0.0), st.floats(0, 20_000))), + startup_period=draw(st.booleans()), + hours=draw(st.sampled_from([0.0, 10.0, 15.9, 16.0, 35.0, 45.0])), + income_rise=draw(st.one_of(st.just(0.0), st.floats(0, 40_000))), + approved_training=False, + ) + for _ in range(draw(st.integers(1, 2))) + ] + # A young person aged 16-19 in the non-dependant family: in full-time + # education (a student) or on approved training (not a student). Whether + # they are the family's claimant or partner is read from the model's + # is_claimant_or_partner. + if draw(st.booleans()): + training = draw(st.booleans()) + non_dependants.append( + dict( + age=draw(st.integers(16, 19)), + education="NOT_IN_EDUCATION" if training else "UPPER_SECONDARY", + earnings=draw(st.one_of(st.just(0.0), st.floats(0, 20_000))), + other_income=0.0, + self_employment=0.0, + statutory_pay=dict.fromkeys(STATUTORY_PAY, 0.0), + pension_contributions=0.0, + startup_period=False, + hours=draw(st.sampled_from([0.0, 8.0, 16.0, 35.0])), + income_rise=draw(st.one_of(st.just(0.0), st.floats(0, 10_000))), + approved_training=training, + ) + ) + return dict( + country=draw(st.sampled_from(sorted(COUNTRIES))), + claimant_age=claimant_age, + claimant_disability=draw(st.sampled_from(CLAIMANT_DISABILITY)), + benefits={ + benefit: draw(st.sampled_from([0.0, 0.0, 0.0, 1_000.0])) + for benefit in BENEFITS + }, + # Which adult non-dependant reports IS, JSA(IB), ESA(IR) and SPC, if + # anyone does. + reporter=draw(st.sampled_from([None, 0, 1])), + # is_uc_claimant for the non-dependants, by position, where the test + # enters it (explicit_uc_claimants). A flag on the young person alone + # makes them a single claimant beside the couple. + uc_claimants=draw(st.lists(st.booleans(), min_size=3, max_size=3)), + claims_child_benefit=draw(st.sampled_from([True, True, False])), + non_dependants=non_dependants, + ) + + +def build(population, year, raise_income=False, explicit_uc_claimants=False): + """The generated population as a simulation. With explicit_uc_claimants, + the inputs name each family's UC claimants: is_uc_claimant is True for + each claimant and the generated flag for each non-dependant. (An input for some people stores + the default, False, for the rest.) Otherwise the model's default applies: + the claimant and partner of each family.""" + people, benunits, households_, persons = {}, {}, {}, [] + for h, household in enumerate(population): + claimant = f"claimant_{h}" + flag = household["claimant_disability"] + people[claimant] = dict( + age=household["claimant_age"], + current_education="NOT_IN_EDUCATION", + employment_income=0.0, + self_employment_income=0.0, + is_in_approved_training=False, + age_started_or_accepted_current_education_or_training=1_000, + weekly_hours=0.0, + is_blind=flag == "is_blind", + **{ + name: 1_000.0 if flag == name else 0.0 + for name in CLAIMANT_DISABILITY[3:] + if name != "is_blind" + }, + ) + if explicit_uc_claimants: + people[claimant]["is_uc_claimant"] = True + persons.append(dict(household=h, benunit=2 * h, non_dependant=None)) + members = [] + for n, nd in enumerate(household["non_dependants"]): + name = f"non_dependant_{h}_{n}" + other = nd["other_income"] + (nd["income_rise"] if raise_income else 0) + reports = household.get("reporter") == n + people[name] = dict( + age=nd["age"], + current_education=nd["education"], + employment_income=nd["earnings"], + other_investment_income=other, + self_employment_income=nd["self_employment"], + **nd.get("statutory_pay", dict.fromkeys(STATUTORY_PAY, 0.0)), + pension_contributions=nd.get("pension_contributions", 0.0), + uc_is_in_startup_period=nd.get("startup_period", False), + **{ + reported: household["benefits"][benefit] if reports else 0.0 + for benefit, reported in REPORTED.items() + }, + # Held at zero so the oracle knows each person's gross income. + state_pension=0.0, + carers_allowance=0.0, + is_in_approved_training=nd["approved_training"], + # Young people started at 16; adults' start ages don't matter. + age_started_or_accepted_current_education_or_training=16, + weekly_hours=nd["hours"], + is_blind=False, + **{ + benefit: 0.0 + for benefit in CLAIMANT_DISABILITY[3:] + if benefit != "is_blind" + }, + ) + if explicit_uc_claimants: + people[name]["is_uc_claimant"] = household["uc_claimants"][n] + members.append(name) + persons.append( + dict(household=h, benunit=2 * h + 1, non_dependant=nd, position=n) + ) + benunits[f"claimant_unit_{h}"] = dict( + members=[claimant], + benunit_is_rent_liable=True, + **{benefit: 0.0 for benefit in BENEFITS + OTHER_FAMILY_BENEFITS}, + ) + benunits[f"non_dependant_unit_{h}"] = dict( + members=members, + benunit_is_rent_liable=False, + would_claim_child_benefit=household.get("claims_child_benefit", True), + **household["benefits"], + **{benefit: 0.0 for benefit in OTHER_FAMILY_BENEFITS}, + ) + households_[f"household_{h}"] = dict( + members=[claimant, *members], + country=household["country"], + local_authority=COUNTRIES[household["country"]], + ) + situation = dict(people=people, benunits=benunits, households=households_) + return Simulation(situation=for_years(situation, [year])), persons + + +def calculate(sim, year): + names = dict( + person=[ + "housing_benefit_non_dep_deduction_exempt", + "housing_benefit_individual_non_dep_deduction_eligible", + "household_benefits_individual_non_dep_deduction", + "council_tax_reduction_non_dep_deduction_exempt", + "council_tax_reduction_individual_non_dep_deduction_eligible", + "council_tax_reduction_individual_non_dep_deduction", + "is_benunit_head", + "is_claimant_or_partner", + "is_uc_assessed_claimant", + "is_qualifying_young_person_for_child_benefit", + "uc_individual_earned_income", + "is_on_income_support", + "is_on_income_based_jsa", + "is_on_income_related_esa", + "is_on_pension_credit", + ], + benunit=[ + "housing_benefit_non_dep_deductions", + "housing_benefit_non_dep_deductions_claimant_exempt", + "council_tax_reduction_non_dep_deductions", + "universal_credit_pre_benefit_cap", + "council_tax_reduction_applicant_has_non_dep_exemption", + "council_tax_reduction_pensioner", + "council_tax_reduction_joint_liability_share", + "share_of_household_rent", + ], + ) + return { + name: np.asarray(sim.calculate(name, year)) + for entity in names.values() + for name in entity + } + + +def aggregate(individual, persons, couple, benunit_count, each_member): + """Recompute family deductions from individual amounts (invariant 5): the + claimant and partner's higher amount (both where each_member), plus each + other member's.""" + couple_amounts, others = {}, {} + for i, person in enumerate(persons): + target = couple_amounts if couple[i] else others + target.setdefault(person["benunit"], []).append(individual[i]) + own = np.array( + [ + (sum if each_member[b] else max)(couple_amounts.get(b, [0.0])) + + sum(others.get(b, [])) + for b in range(benunit_count) + ] + ) + household_of = {p["benunit"]: p["household"] for p in persons} + totals = {} + for b in range(benunit_count): + totals[household_of[b]] = totals.get(household_of[b], 0.0) + own[b] + return np.array([totals[household_of[b]] - own[b] for b in range(benunit_count)]) + + +@PROPERTY_SETTINGS +@given( + population=st.lists(households(), min_size=1, max_size=12), + year=st.integers(2025, 2030), + explicit_uc_claimants=st.booleans(), +) +@example( + # Rare under random draws: a Welsh working-age applicant's non-dependant + # couple on Universal Credit, both with earnings (Sch 6 para 5(3)); and + # the Housing Benefit under-25 boundary on Income Support (reg 74(8)). + population=[ + dict( + country="WALES", + claimant_age=60, + claimant_disability=None, + benefits={ + **dict.fromkeys(BENEFITS, 0.0), + "universal_credit_pre_benefit_cap": 1_000.0, + }, + non_dependants=[ + dict( + age=30, + education="NOT_IN_EDUCATION", + earnings=15_600.0, + other_income=0.0, + hours=35.0, + income_rise=0.0, + self_employment=0.0, + approved_training=False, + ), + dict( + age=28, + education="NOT_IN_EDUCATION", + earnings=1_000.0, + other_income=0.0, + hours=0.0, + income_rise=5_000.0, + self_employment=0.0, + approved_training=False, + ), + ], + ), + *[ + dict( + country="ENGLAND", + claimant_age=70, + claimant_disability=None, + benefits={**dict.fromkeys(BENEFITS, 0.0), "income_support": 1_000.0}, + non_dependants=[ + dict( + age=age, + education="NOT_IN_EDUCATION", + earnings=0.0, + other_income=0.0, + hours=0.0, + income_rise=0.0, + self_employment=0.0, + approved_training=False, + ) + ], + ) + for age in [24, 25] + ], + ], + year=2026, + explicit_uc_claimants=False, +) +def test_non_dependant_deduction_invariants(population, year, explicit_uc_claimants): + sim, persons = build(population, year, explicit_uc_claimants=explicit_uc_claimants) + r = calculate(sim, year) + p = SYSTEM.parameters(str(year)) + hb = p.gov.dwp.housing_benefit.non_dep_deduction + hb_lowest, hb_top = hb.amount.amounts[0] * WEEKS, hb.amount.amounts[-1] * WEEKS + # Each applicant's scheme follows its own family; the applicant is each + # household's claimant family (benefit units 0, 2, 4, ...). + applicant_pensioner = r["council_tax_reduction_pensioner"].astype(bool)[0::2] + pension_age = np.repeat( + applicant_pensioner, + [1 + len(h["non_dependants"]) for h in population], + ) + # Claimant or partner of each person's own family (SSCBA s.137 couple). + couple = r["is_claimant_or_partner"].astype(bool) + members_of = {} + for i, person in enumerate(persons): + members_of.setdefault(person["benunit"], []).append(i) + # Universal Credit's claimants: the members is_uc_claimant flags (by + # default the claimant and partner), the two eldest where inputs flag + # more (WRA 2012 ss. 2(1), 40), in the order entered at equal ages. + assessed = r["is_uc_assessed_claimant"].astype(bool) + for members in members_of.values(): + household = population[persons[members[0]]["household"]] + flags = household.get("uc_claimants") + + def flagged(j): + if not explicit_uc_claimants: + return bool(couple[j]) + if persons[j]["non_dependant"] is None: + return True + return flags[persons[j]["position"]] + + def age(j): + nd = persons[j]["non_dependant"] + return household["claimant_age"] if nd is None else nd["age"] + + eldest = sorted(filter(flagged, members), key=lambda j: -age(j))[:2] + assert sorted(j for j in members if assessed[j]) == sorted(eldest) + # Whether a family's Universal Credit is the couple's own: it assesses + # the claimant or partner. + couple_claims_universal_credit = { + b: any(couple[j] and assessed[j] for j in members) + for b, members in members_of.items() + } + + def payee_of(i, benefit): + """The award's payee: the member who reports it, or the head.""" + person = persons[i] + household = population[person["household"]] + reporter = household.get("reporter") + reported = ( + person["non_dependant"] is not None + and reporter is not None + and reporter < len(household["non_dependants"]) + and household["benefits"][benefit] > 0 + ) + if reported: + return person["position"] == reporter + return bool(r["is_benunit_head"][i]) + + f32 = np.float32 + + def own_gross(j): + """A person's own gross income from the generated inputs (invariant + 11): a self-employment loss counts as nil.""" + nd_j = persons[j]["non_dependant"] + if nd_j is None: + return 0.0 + return float( + f32(nd_j["earnings"]) + + sum( + f32(nd_j.get("statutory_pay", {}).get(name, 0.0)) + for name in STATUTORY_PAY + ) + + max(f32(0), f32(nd_j["self_employment"])) + + f32(nd_j["other_income"]) + ) + + def band_amount(scale, weekly): + """The amount of the band whose lower edge is the highest not above + weekly income, or None within a penny of an edge.""" + if any(abs(weekly - t) < 0.01 for t in scale.thresholds[1:]): + return None + index = max(k for k, t in enumerate(scale.thresholds) if t <= weekly) + return scale.amounts[index] * WEEKS + + for i, person in enumerate(persons): + household = population[person["household"]] + hb_deduction = r["household_benefits_individual_non_dep_deduction"][i] + ctr_deduction = r["council_tax_reduction_individual_non_dep_deduction"][i] + # 1. Exempt => 0. + if r["housing_benefit_non_dep_deduction_exempt"][i]: + assert hb_deduction == 0 + if r["council_tax_reduction_non_dep_deduction_exempt"][i]: + assert ctr_deduction == 0 + nd = person["non_dependant"] + if nd is None: + continue + # 9. The exemptions, read straight from the inputs. + # IS, JSA(IB), ESA(IR) and SPC count for their payee (HB reg 2(1), + # (3)-(3A)): the model's is_on_* status. Where the reporter, if any, is + # the claimant or partner, that is the reporter, or the benefit unit's + # head where none reports. Universal Credit counts for the claimants + # it assesses. + on = {b: bool(r[IS_ON[b]][i]) for b in REPORTED} + reporter = household.get("reporter") + reporter_index = ( + None + if reporter is None or reporter >= len(household["non_dependants"]) + else members_of[person["benunit"]][reporter] + ) + # Pension Credit's payee must also have reached the qualifying age + # (SPCA 2002 s.1(2)(b)), which payee_of doesn't model. + if reporter_index is None or couple[reporter_index]: + for b in ["income_support", "jsa_income", "esa_income"]: + assert on[b] == (payee_of(i, b) and household["benefits"][b] > 0), ( + b, + i, + ) + on["universal_credit_pre_benefit_cap"] = ( + bool(assessed[i]) + and household["benefits"]["universal_credit_pre_benefit_cap"] > 0 + ) + student = nd["education"] != "NOT_IN_EDUCATION" + # Earned income is Universal Credit's own calculation (HB reg 74(11), + # CTR Sch 1 para 8(11A) and equivalents: UC Regs 2013 reg 52), which + # invariant 13 checks against the inputs. + earned = r["uc_individual_earned_income"][i] > 0 + uc_without_earnings = on["universal_credit_pre_benefit_cap"] and not earned + hb_exempt = ( + student + or on["pension_credit"] + or ( + nd["age"] < 25 + and (on["income_support"] or on["jsa_income"] or uc_without_earnings) + ) + ) + assert bool(r["housing_benefit_non_dep_deduction_exempt"][i]) == hb_exempt + national = household["country"] != "ENGLAND" or pension_age[i] + legacy = ["income_support", "jsa_income", "esa_income"] + on_legacy = any(on[b] for b in legacy) + # Scotland's working-age scheme (SSI 2021/249, from April 2022): a + # non-dependant who is, or whose partner is, on IS, JSA(IB) or ESA(IR) + # and who is not on Universal Credit. + if household["country"] == "SCOTLAND" and not pension_age[i]: + couple_on_legacy = bool(couple[i]) and any( + couple[j] and any(r[IS_ON[b]][j] for b in legacy) + for j in members_of[person["benunit"]] + ) + qualifying = (on_legacy or couple_on_legacy) and not on[ + "universal_credit_pre_benefit_cap" + ] + else: + qualifying = on_legacy + ctr_exempt = ( + student + or qualifying + or on["pension_credit"] + or (national and uc_without_earnings) + # LGFA 1992 Sch 1 para 3: an adult in someone else's family for + # whom child benefit is claimed. + or ( + nd["age"] >= 18 + and bool(r["is_qualifying_young_person_for_child_benefit"][i]) + and not couple[i] + and household.get("claims_child_benefit", True) + ) + ) + assert ( + bool(r["council_tax_reduction_non_dep_deduction_exempt"][i]) == ctr_exempt + ) + + # 2-3. Housing Benefit bounds and the not-in-work amount. + if ( + r["housing_benefit_individual_non_dep_deduction_eligible"][i] + and not r["housing_benefit_non_dep_deduction_exempt"][i] + ): + assert hb_lowest - 0.01 <= hb_deduction <= hb_top + 0.01 + # Reg 6(6): on IS, JSA(IB) or ESA(IR) is not remunerative work. + if ( + nd["hours"] < hb.remunerative_work_hours + or on["income_support"] + or on["jsa_income"] + or on["esa_income"] + ): + assert hb_deduction == pytest.approx(hb_lowest, abs=0.01) + # 2-3. Council Tax Reduction bounds and the not-in-work amount. + country = household["country"] + if not national: + assert ctr_deduction == 0 + elif ( + r["council_tax_reduction_individual_non_dep_deduction_eligible"][i] + and not r["council_tax_reduction_non_dep_deduction_exempt"][i] + ): + scale = p.gov.local_authorities + for part in CTR_SCALES[country].split("."): + scale = getattr(scale, part) + lowest = scale.amount.amounts[0] * WEEKS + top = scale.amount.amounts[-1] * WEEKS + assert lowest - 0.01 <= ctr_deduction <= top + 0.01 + if nd["hours"] < scale.remunerative_work_hours: + assert ctr_deduction == pytest.approx(lowest, abs=0.01) + + # 11. Banding on gross income computed from the inputs. Income-based + # JSA and income-related ESA count only where the claimant or partner + # reports them, or no one does. + couple_awards = ( + ["jsa_income", "esa_income"] + if reporter_index is None or couple[reporter_index] + else [] + ) + family_awards = sum( + household["benefits"][b] + for b in ["income_support", "pension_credit", *couple_awards] + ) + # Universal Credit is the income of the claimants it assesses: the + # couple's, or a single claimant's beside it. + universal_credit = household["benefits"]["universal_credit_pre_benefit_cap"] + couple_claims = couple_claims_universal_credit[person["benunit"]] + if couple[i]: + weekly = ( + sum(own_gross(j) for j in members_of[person["benunit"]] if couple[j]) + + family_awards + + (universal_credit if couple_claims else 0.0) + ) / WEEKS + else: + weekly = ( + own_gross(i) + + (universal_credit if assessed[i] and not couple_claims else 0.0) + ) / WEEKS + if ( + r["housing_benefit_individual_non_dep_deduction_eligible"][i] + and nd["hours"] >= hb.remunerative_work_hours + and not (on["income_support"] or on["jsa_income"] or on["esa_income"]) + ): + expected = band_amount(hb.amount, weekly) + if expected is not None: + assert hb_deduction == pytest.approx(expected, abs=0.01), (i, weekly) + if ( + national + and r["council_tax_reduction_individual_non_dep_deduction_eligible"][i] + and not r["council_tax_reduction_non_dep_deduction_exempt"][i] + and nd["hours"] >= scale.remunerative_work_hours + and not qualifying + ): + expected = band_amount(scale.amount, weekly) + if expected is not None: + assert ctr_deduction == pytest.approx(expected, abs=0.01), (i, weekly) + + benunit_count = 2 * len(population) + # 4-5. Housing Benefit aggregation and the claimant exemption. + expected_hb = aggregate( + r["household_benefits_individual_non_dep_deduction"], + persons, + couple, + benunit_count, + [False] * benunit_count, + ) + # 4. The claimant exemption, from the generated disability inputs: only + # the claimant family's claimant can be blind or on a qualifying benefit. + disabled = np.array([h["claimant_disability"] is not None for h in population]) + claimant_exempt = np.repeat(disabled, 2) & (np.arange(benunit_count) % 2 == 0) + np.testing.assert_array_equal( + r["housing_benefit_non_dep_deductions_claimant_exempt"].astype(bool), + claimant_exempt, + ) + expected_hb = np.where( + claimant_exempt, 0, expected_hb * r["share_of_household_rent"] + ) + np.testing.assert_allclose( + r["housing_benefit_non_dep_deductions"], expected_hb, atol=0.01 + ) + # 4-5. Council Tax Reduction aggregation and the applicant exemption. + welsh_working_age = [ + population[b // 2]["country"] == "WALES" + and not applicant_pensioner[b // 2] + and couple_claims_universal_credit[b] + and r["universal_credit_pre_benefit_cap"][b] > 0 + for b in range(benunit_count) + ] + expected_ctr = aggregate( + r["council_tax_reduction_individual_non_dep_deduction"], + persons, + couple, + benunit_count, + welsh_working_age, + ) + # The applicant is the claimant family (its member is the oldest). + np.testing.assert_array_equal( + r["council_tax_reduction_applicant_has_non_dep_exemption"].astype(bool), + claimant_exempt, + ) + expected_ctr = np.where( + claimant_exempt, + 0, + expected_ctr * r["council_tax_reduction_joint_liability_share"], + ) + np.testing.assert_allclose( + r["council_tax_reduction_non_dep_deductions"], expected_ctr, atol=0.01 + ) + + # 6. Raising non-dependants' incomes never lowers a deduction. + raised, _ = build( + population, + year, + raise_income=True, + explicit_uc_claimants=explicit_uc_claimants, + ) + s = calculate(raised, year) + for name in [ + "household_benefits_individual_non_dep_deduction", + "council_tax_reduction_individual_non_dep_deduction", + "housing_benefit_non_dep_deductions", + "council_tax_reduction_non_dep_deductions", + ]: + assert np.all(s[name] >= r[name] - 1e-3), name + + +def first_income_at_or_above(threshold): + """The smallest float32 annual income (the input's storage type) whose + weekly value, computed as the model does (the benefit-unit sum returns + float64, then divided by 52), is at least the threshold.""" + + def weekly(annual): + return float(np.float32(annual)) / WEEKS + + income = np.float32(threshold * WEEKS) + while weekly(income) < threshold: + income = np.nextafter(income, np.float32(np.inf)) + while weekly(np.nextafter(income, np.float32(-np.inf))) >= threshold: + income = np.nextafter(income, np.float32(-np.inf)) + return income + + +SCALES = [ + ("HOUSING_BENEFIT", range(2019, 2031)), + ("ENGLAND", range(2015, 2031)), + ("WALES", range(2015, 2031)), + ("SCOTLAND", range(2015, 2031)), +] + + +@pytest.mark.parametrize("scale_name, years", SCALES, ids=[s for s, _ in SCALES]) +def test_band_edges_are_inclusive(scale_name, years): + """Invariant 7: every lower edge, every year, both schemes.""" + country = "ENGLAND" if scale_name == "HOUSING_BENEFIT" else scale_name + edges_by_year = {} + for year in years: + p = SYSTEM.parameters(str(year)) + if scale_name == "HOUSING_BENEFIT": + scale = p.gov.dwp.housing_benefit.non_dep_deduction.amount + else: + scale = p.gov.local_authorities + for part in CTR_SCALES[scale_name].split("."): + scale = getattr(scale, part) + scale = scale.amount + edges_by_year[year] = list( + zip(scale.thresholds[1:], scale.amounts[1:], scale.amounts[:-1]) + ) + count = max(len(edges) for edges in edges_by_year.values()) + people, benunits, households_ = {}, {}, {} + for k in range(2 * count): + claimant, non_dependant = f"claimant_{k}", f"non_dependant_{k}" + incomes = {} + for year, edges in edges_by_year.items(): + threshold, _, _ = edges[k // 2] + at_edge = first_income_at_or_above(threshold) + incomes[str(year)] = float( + at_edge if k % 2 == 0 else np.nextafter(at_edge, np.float32(-np.inf)) + ) + people[claimant] = dict(age=80, current_education="NOT_IN_EDUCATION") + people[non_dependant] = dict( + age=40, + current_education="NOT_IN_EDUCATION", + weekly_hours={str(y): 35.0 for y in years}, + housing_benefit_non_dep_gross_income=incomes, + council_tax_reduction_non_dep_gross_income=incomes, + ) + benunits[f"claimant_unit_{k}"] = dict( + members=[claimant], + benunit_is_rent_liable={str(y): True for y in years}, + **{b: {str(y): 0.0 for y in years} for b in BENEFITS}, + ) + benunits[f"non_dependant_unit_{k}"] = dict( + members=[non_dependant], + benunit_is_rent_liable={str(y): False for y in years}, + **{b: {str(y): 0.0 for y in years} for b in BENEFITS}, + ) + households_[f"household_{k}"] = dict( + members=[claimant, non_dependant], + country=country, + local_authority=COUNTRIES[country], + ) + sim = Simulation( + situation=for_years( + dict(people=people, benunits=benunits, households=households_), years + ) + ) + variable = ( + "household_benefits_individual_non_dep_deduction" + if scale_name == "HOUSING_BENEFIT" + else "council_tax_reduction_individual_non_dep_deduction" + ) + for year, edges in edges_by_year.items(): + deductions = np.asarray(sim.calculate(variable, year))[1::2] / WEEKS + for k, (threshold, amount, below) in enumerate(edges): + assert deductions[2 * k] == pytest.approx(amount, abs=1e-4), ( + year, + threshold, + ) + assert deductions[2 * k + 1] == pytest.approx(below, abs=1e-4), ( + year, + threshold, + ) + + +def test_ctr_scales_match_enacted_sums(): + """Invariant 8: the parameters reproduce the enacted table.""" + fixture = yaml.safe_load(FIXTURE.read_text()) + for scheme, entry in fixture.items(): + for year, row in entry["years"].items(): + # PolicyEngine UK reads parameters at 30 April from 2015; earlier + # years are read at that date directly. + instant = str(year) if year >= 2015 else f"{year}-04-30" + node = SYSTEM.parameters(instant) + for part in entry["parameter"].split("."): + node = getattr(node, part) + assert list(node.thresholds) == pytest.approx(row["thresholds"]), ( + scheme, + year, + ) + assert list(node.amounts) == pytest.approx(row["amounts"]), (scheme, year) + + +def capped_universal_credit_families(families, year, **extra): + """One household per family: the generated awards and debt deduction + rate, no Housing Benefit and no Maternity Allowance.""" + people, benunits, households_ = {}, {}, {} + for f, family in enumerate(families): + members = [f"adult_{f}_{k}" for k in range(2 if family["couple"] else 1)] + for k, name in enumerate(members): + people[name] = dict( + age=30 - k, + jsa_contrib=family["jsa_contrib"] if k == 0 else 0.0, + esa_contrib=family["esa_contrib"] if k == 0 else 0.0, + maternity_allowance=0.0, + ) + rate = family.get("uc_deduction_rate", 0.0) + benunits[f"family_{f}"] = dict( + members=members, + **{ + key: family[key] + for key in [ + "universal_credit_pre_benefit_cap", + "child_benefit", + "child_tax_credit", + "uc_benefit_cap", + "uc_childcare_element", + ] + }, + income_support=0.0, + jsa_income=0.0, + esa_income=0.0, + housing_benefit_pre_benefit_cap=0.0, + uc_deduction_rate=rate, + **(dict() if rate else dict(uc_deductions=0.0)), + **extra, + ) + households_[f"household_{f}"] = dict(members=members, country="ENGLAND") + return for_years( + dict(people=people, benunits=benunits, households=households_), [year] + ) + + +def capped_universal_credit(sim, year): + from policyengine_uk.variables.gov.dwp.housing_benefit.non_dep_deduction._non_dependants import ( + universal_credit_award, + ) + + return universal_credit_award(sim.populations["benunit"], year) + + +def core_simulation(situation, year, system=SYSTEM, trace=False): + """A simulation on a tax-benefit system built once for the module.""" + from policyengine_core.simulations import Simulation as CoreSimulation + + return CoreSimulation( + tax_benefit_system=system, + situation=situation, + default_input_period=year, + default_calculation_period=year, + trace=trace, + ) + + +CAPPED_FAMILY = st.fixed_dictionaries( + dict( + couple=st.booleans(), + universal_credit_pre_benefit_cap=st.one_of(st.just(0.0), st.floats(0, 40_000)), + child_benefit=st.sampled_from([0.0, 1_500.0, 5_000.0]), + child_tax_credit=st.sampled_from([0.0, 3_000.0]), + uc_benefit_cap=st.sampled_from([14_753.0, 22_020.0, 25_323.0]), + uc_childcare_element=st.sampled_from([0.0, 2_000.0, 12_000.0]), + jsa_contrib=st.sampled_from([0.0, 4_000.0]), + esa_contrib=st.sampled_from([0.0, 5_000.0]), + uc_deduction_rate=st.sampled_from([0.0, 0.05, 0.15]), + ) +) +ONE_CAPPED_FAMILY = dict( + couple=False, + universal_credit_pre_benefit_cap=20_000.0, + child_benefit=0.0, + child_tax_credit=0.0, + uc_benefit_cap=14_753.0, + uc_childcare_element=0.0, + jsa_contrib=0.0, + esa_contrib=0.0, +) + + +PROTECTED_FLOOR = "gov.dwp.universal_credit.deductions.protected_floor" + + +@PROPERTY_SETTINGS +@given( + families=st.lists(CAPPED_FAMILY, min_size=1, max_size=8), + year=st.integers(2025, 2030), +) +@example( + # Under the 85% floor the 15% debt deduction comes first and the cap + # reduction goes, so the award before deductions is the full 20,000. + families=[ONE_CAPPED_FAMILY, dict(ONE_CAPPED_FAMILY, uc_deduction_rate=0.15)], + year=2025, +) +def test_capped_universal_credit_matches_the_model(families, year): + """Invariant 12: the Universal Credit cap counts the welfare benefits + other than Housing Benefit, and a non-dependant family's gross income + counts its award after the cap and before debt deductions, under current + law and under an 85% protected minimum floor, which never lowers an + award.""" + + def check(): + sim = core_simulation(capped_universal_credit_families(families, year), year) + # UC Regs 2013 reg 80(2A), with no Housing Benefit to take out. + np.testing.assert_array_equal( + sim.calculate("uc_benefit_cap_welfare_benefits", year), + sim.calculate("benefit_cap_welfare_benefits", year) + - sim.calculate("housing_benefit_pre_benefit_cap", year), + ) + np.testing.assert_allclose( + capped_universal_credit(sim, year), + np.maximum( + sim.calculate("universal_credit_pre_benefit_cap", year) + - sim.calculate("uc_benefit_cap_reduction_after_floor", year), + 0, + ), + atol=0.01, + ) + return np.asarray(sim.calculate("universal_credit", year)) + + current_law = check() + floor = SYSTEM.parameters.get_child(PROTECTED_FLOOR) + floor.update(period=str(year), value=0.85) + SYSTEM.reset_parameter_caches() + try: + floored = check() + finally: + floor.update(period=str(year), value=0.0) + SYSTEM.reset_parameter_caches() + assert np.all(floored >= current_law - 0.01) + + +class uc_award_scale(Variable): + value_type = float + entity = BenUnit + label = "Scale on the Universal Credit award, for a test reform" + definition_period = YEAR + default_value = 1.0 + + +@lru_cache(maxsize=None) +def scaled_award_system(): + """Current law, with universal_credit replaced by uc_award_scale times + current law's formula. The replacement still reads the cap reduction.""" + from policyengine_core.reforms import Reform + + from policyengine_uk.variables.gov.dwp.universal_credit.universal_credit import ( + universal_credit as current_law, + ) + + def formula(benunit, period, parameters): + return benunit("uc_award_scale", period) * current_law.formula( + benunit, period, parameters + ) + + class ScaledAward(Reform): + def apply(self): + self.add_variable(uc_award_scale) + self.update_variable( + type("universal_credit", (current_law,), dict(formula=formula)) + ) + + return ScaledAward(SYSTEM) + + +@pytest.fixture(scope="module", autouse=True) +def release_scaled_award_system(): + # The system also keeps its last simulation alive. Clear the cache after + # this module's last test, so the system doesn't stay in memory for the + # rest of the test process. + yield + scaled_award_system.cache_clear() + + +AWARD_YEAR = 2025 +AWARD_HOUSEHOLD = st.fixed_dictionaries( + dict( + employment_income=st.floats(0, 20_000), + universal_credit_pre_benefit_cap=st.floats(0, 25_000), + scale=st.floats(0, 1), + ) +) +# 4,000 of wages plus the 14,753 capped award come to 360.63 a week, in the +# 348 band; a tenth less award gives 332.26 a week, in the 266 band. +BAND_CROSSING = dict( + employment_income=4_000.0, universal_credit_pre_benefit_cap=15_000.0, scale=0.9 +) + + +def award_households(households, awards=None, scaled=True): + """Each generated household twice, with the generated scale and then + with none: a 70-year-old claimant paying 6,000 rent, and a 40-year-old + non-dependant working 20 hours who claims Universal Credit under a 14,753 + cap. awards supplies each family's universal_credit; scaled=False leaves + out the scale, for current law.""" + people, benunits, households_ = {}, {}, {} + no_awards = {benefit: 0.0 for benefit in BENEFITS + OTHER_FAMILY_BENEFITS} + for copy, with_scale in enumerate([True, False]): + for h, household in enumerate(households): + claimant, non_dependant = f"claimant_{copy}_{h}", f"non_dep_{copy}_{h}" + people[claimant] = dict(age=70, current_education="NOT_IN_EDUCATION") + people[non_dependant] = dict( + age=40, + current_education="NOT_IN_EDUCATION", + employment_income=household["employment_income"], + weekly_hours=20.0, + ) + benunits[f"claimant_unit_{copy}_{h}"] = dict( + members=[claimant], benunit_is_rent_liable=True, **no_awards + ) + benunits[f"non_dep_unit_{copy}_{h}"] = dict( + members=[non_dependant], + benunit_is_rent_liable=False, + would_claim_uc=True, + uc_benefit_cap=14_753.0, + uc_childcare_element=0.0, + **{ + **no_awards, + "universal_credit_pre_benefit_cap": household[ + "universal_credit_pre_benefit_cap" + ], + }, + ) + if awards is None and scaled: + benunits[f"non_dep_unit_{copy}_{h}"]["uc_award_scale"] = ( + household["scale"] if with_scale else 1.0 + ) + households_[f"household_{copy}_{h}"] = dict( + members=[claimant, non_dependant], + country="ENGLAND", + local_authority="MAIDSTONE", + rent=6_000.0, + tenure_type="RENT_PRIVATELY", + ) + if awards is not None: + for name, award in zip(benunits, awards): + benunits[name]["universal_credit"] = float(award) + return for_years( + dict(people=people, benunits=benunits, households=households_), [AWARD_YEAR] + ) + + +def test_universal_credit_reads_no_calculated_housing_benefit(): + """Invariant 12: Universal Credit's traced calculation reads no Housing + Benefit variable but the reported award, so the deductions can read + universal_credit. Inputs that supply benefit_cap_welfare_benefits, a + total that includes Housing Benefit, also supply + housing_benefit_pre_benefit_cap or uc_benefit_cap_welfare_benefits, or + the basket raises ValueError. Where they supply + housing_benefit_pre_benefit_cap, Universal Credit reads that input + without calculating Housing Benefit.""" + year = 2025 + situation = award_households([BAND_CROSSING], scaled=False) + sim = core_simulation(situation, year, trace=True) + sim.calculate("universal_credit", year) + read = {key.split("<")[0] for key in sim.tracer.get_serialized_flat_trace()} + assert "universal_credit" in read + assert not [ + name + for name in read + if name.startswith("housing_benefit") and name != "housing_benefit_reported" + ], read + for unit in situation["benunits"].values(): + unit["benefit_cap_welfare_benefits"] = { + str(year): unit["universal_credit_pre_benefit_cap"][str(year)] + } + with pytest.raises(ValueError, match="housing_benefit_pre_benefit_cap"): + core_simulation(situation, year).calculate("housing_benefit", year) + for unit in situation["benunits"].values(): + unit["housing_benefit_pre_benefit_cap"] = {str(year): 0.0} + sim = core_simulation(situation, year, trace=True) + sim.calculate("universal_credit", year) + read = {key.split("<")[0] for key in sim.tracer.get_serialized_flat_trace()} + assert not [ + name + for name in read + if name.startswith("housing_benefit") + and name not in ("housing_benefit_reported", "housing_benefit_pre_benefit_cap") + ], read + + +@PROPERTY_SETTINGS +@given(households=st.lists(AWARD_HOUSEHOLD, min_size=1, max_size=3)) +@example(households=[BAND_CROSSING]) +def test_deduction_follows_a_reformed_award(households): + """Invariant 12: under a reform that scales the Universal Credit award, + each household's Housing Benefit and CTR deductions equal those of the + same household with the reformed award supplied, and the Housing Benefit + deduction is the band of the non-dependant's wages plus that award, so + an award that crosses a band moves the deduction.""" + year = AWARD_YEAR + reformed = core_simulation( + award_households(households), year, system=scaled_award_system() + ) + awards = np.asarray(reformed.calculate("universal_credit", year)) + supplied = core_simulation(award_households(households, awards), year) + for name in [ + "housing_benefit_non_dep_deductions", + "council_tax_reduction_non_dep_deductions", + "housing_benefit", + ]: + np.testing.assert_allclose( + reformed.calculate(name, year), + supplied.calculate(name, year), + atol=0.01, + err_msg=name, + ) + # Reg 74(1)(a), (2): the non-dependant works 20 hours and has no other + # income, so the deduction is the band of wages plus the award. + scale = SYSTEM.parameters(str(year)).gov.dwp.housing_benefit.non_dep_deduction + deductions = np.asarray( + reformed.calculate("housing_benefit_non_dep_deductions", year) + ) + wages = [h["employment_income"] for h in households] * 2 + for i, (wage, award) in enumerate(zip(wages, awards[1::2])): + weekly = (wage + award) / WEEKS + if min(abs(weekly - edge) for edge in scale.amount.thresholds) < 0.01: + continue + expected = scale.amount.calc(np.array([weekly]))[0] * WEEKS + assert deductions[2 * i] == pytest.approx(expected, abs=0.01), (i, weekly) + if households == [BAND_CROSSING]: + assert deductions[0] < deductions[2], deductions + + +# Earned-income cases for invariant 13. Each sets the person's earnings +# inputs; "settled" is what the inputs alone say, where they say it without +# the tax and National Insurance calculation: no earnings left after +# expenses, pension contributions and trading losses (False), or a small +# positive amount no deduction can remove (True). +EARNINGS = { + "none": (dict(), False), + "wages": (dict(employment_income=1_000.0), True), + "wages_cancelled_by_expenses": ( + dict(employment_income=1_000.0, employment_expenses=1_000.0), + False, + ), + "wages_partly_cancelled_by_expenses": ( + dict(employment_income=1_000.0, employment_expenses=500.0), + True, + ), + "wages_into_pension": ( + dict(employment_income=1_000.0, employee_pension_contributions=1_000.0), + None, + ), + "profit": (dict(self_employment_income=1_000.0), None), + "profit_cancelled_by_loss": ( + dict(self_employment_income=1_000.0, trading_loss=1_000.0), + None, + ), + "loss": (dict(self_employment_income=-500.0), None), + "company_income": ( + dict( + stands_as_sole_owner_or_partner_of_company=True, + owned_company_carries_on_trade=True, + owned_company_income_share=1_000.0, + ), + True, + ), + "company_income_without_owner_treatment": ( + dict(owned_company_carries_on_trade=True, owned_company_income_share=1_000.0), + False, + ), + "high_wages": (dict(employment_income=30_000.0), True), +} + + +@PROPERTY_SETTINGS +@given( + families=st.lists( + st.lists( + st.fixed_dictionaries( + dict( + age=st.integers(18, 89), + earnings=st.sampled_from(sorted(EARNINGS)), + # None leaves the minimum income floor to the model. + floor_applies=st.sampled_from([None, None, True, False]), + ) + ), + min_size=1, + max_size=2, + ), + min_size=1, + max_size=6, + ), + year=st.integers(2025, 2030), +) +@example( + # A couple's floor: the partner's earnings meet the couple threshold, so + # the floor doesn't apply to the other member (UC Regs 2013 reg 62(3)). + families=[ + [ + dict(age=24, earnings="none", floor_applies=True), + dict(age=30, earnings="high_wages", floor_applies=False), + ], + [dict(age=24, earnings="none", floor_applies=True)], + ], + year=2026, +) +def test_earned_income_exemption_reads_universal_credit(families, year): + """Invariant 13: compare the Universal Credit exemption's earned-income test + with Universal Credit's own calculation (HB Regs 2006 reg 74(8)(b), (11); WSI + 2013/3029 Sch 1 para 3(8)(c), (10)).""" + people, benunits, households_, cases = {}, {}, {}, [] + for f, family in enumerate(families): + claimant = f"claimant_{f}" + people[claimant] = dict(age=90, current_education="NOT_IN_EDUCATION") + cases.append(None) + members = [] + for k, member in enumerate(family): + name = f"member_{f}_{k}" + inputs, settled = EARNINGS[member["earnings"]] + people[name] = dict( + age=member["age"], + current_education="NOT_IN_EDUCATION", + weekly_hours=0.0, + **inputs, + ) + if member["floor_applies"] is not None: + people[name]["uc_mif_applies"] = member["floor_applies"] + people[name]["uc_minimum_income_floor"] = 10_000.0 + members.append(name) + cases.append((member, settled)) + no_awards = {benefit: 0.0 for benefit in BENEFITS + OTHER_FAMILY_BENEFITS} + benunits[f"claimant_unit_{f}"] = dict( + members=[claimant], benunit_is_rent_liable=True, **no_awards + ) + benunits[f"family_{f}"] = dict( + members=members, + benunit_is_rent_liable=False, + **{**no_awards, "universal_credit_pre_benefit_cap": 1_000.0}, + ) + households_[f"household_{f}"] = dict( + members=[claimant, *members], + country="WALES", + local_authority=COUNTRIES["WALES"], + ) + sim = Simulation( + situation=for_years( + dict(people=people, benunits=benunits, households=households_), [year] + ) + ) + earned = np.asarray(sim.calculate("uc_individual_earned_income", year)) > 0 + assessed = np.asarray(sim.calculate("is_uc_assessed_claimant", year)) + floor = np.asarray(sim.calculate("uc_mif_applies", year)) + hb_exempt = np.asarray( + sim.calculate("housing_benefit_non_dep_deduction_exempt", year) + ) + ctr_exempt = np.asarray( + sim.calculate("council_tax_reduction_non_dep_deduction_exempt", year) + ) + for i, case in enumerate(cases): + if case is None: + continue + member, settled = case + # For each claimant the award assesses, compare the exemption with + # Universal Credit's earned-income calculation (the model pairs + # claimant and partner, and may leave a second adult outside the + # couple): the Welsh pensioner scheme at any age, Housing Benefit + # under 25. + on_universal_credit = bool(assessed[i]) + uc_without_earnings = on_universal_credit and not earned[i] + assert bool(ctr_exempt[i]) == uc_without_earnings, (i, member) + assert bool(hb_exempt[i]) == (member["age"] < 25 and uc_without_earnings), ( + i, + member, + ) + # Where the inputs settle it, and the floor doesn't apply. + if settled is not None and not floor[i]: + assert bool(earned[i]) == settled, (i, member) diff --git a/policyengine_uk/tests/test_non_dependant_population_consistent_total.py b/policyengine_uk/tests/test_non_dependant_population_consistent_total.py new file mode 100644 index 0000000000..074d84ad24 --- /dev/null +++ b/policyengine_uk/tests/test_non_dependant_population_consistent_total.py @@ -0,0 +1,104 @@ +"""Supplied cap totals that agree with each household's own calculation. + +Each household's combined total and Housing Benefit come from its own +simulation with nothing supplied. Supplying both for a population of those +households gives every household the results it gets alone, whatever the +first request asks for. Supplying the totals without the Housing Benefit +raises ValueError (test_non_dependant_cycle_contracts.py states the +contract). +""" + +import numpy as np +import pytest + +from policyengine_uk import CountryTaxBenefitSystem +from policyengine_uk.tests.test_non_dependant_cycle_contracts import ( + FIRST_REQUESTS, + YEAR, + make, + situation, + supply, +) + +HOUSEHOLDS = ["single", "nondep"] + + +@pytest.fixture(scope="module") +def system(): + return CountryTaxBenefitSystem() + + +@pytest.fixture(scope="module") +def standalone(system): + """Each household's own totals and results, with nothing supplied.""" + results = {} + for which in HOUSEHOLDS: + sim = make(system, situation(which)) + units = list(situation(which)["benunits"]) + results[which] = { + name: dict(zip(units, sim.calculate(name, YEAR).tolist())) + for name in [ + "benefit_cap_welfare_benefits", + "housing_benefit_pre_benefit_cap", + "universal_credit", + "housing_benefit", + ] + } + return results + + +def consistent(standalone, *variables): + """The mixed population with ``variables`` supplied at each household's + own values.""" + data = situation("mixed") + for variable in variables: + values = {} + for which in HOUSEHOLDS: + values.update(standalone[which][variable]) + supply(data, variable, values) + return data + + +def expected(standalone, name): + return [value for which in HOUSEHOLDS for value in standalone[which][name].values()] + + +def test_each_household_alone(standalone): + # The claimant pays no rent, so the total is the £20,000 of Universal + # Credit; the applicant's total is their own Housing Benefit, before a + # cap that pension age keeps away. + assert expected(standalone, "benefit_cap_welfare_benefits") == pytest.approx( + [20_000.0, 4_978.20, 0.0], abs=0.01 + ) + assert expected(standalone, "housing_benefit_pre_benefit_cap") == pytest.approx( + [0.0, 4_978.20, 0.0], abs=0.01 + ) + assert expected(standalone, "universal_credit") == pytest.approx( + [14_753.0, 0.0, 0.0], abs=0.01 + ) + + +@pytest.mark.parametrize("first", FIRST_REQUESTS) +def test_consistent_totals_keep_households_independent(system, standalone, first): + sim = make( + system, + consistent( + standalone, + "benefit_cap_welfare_benefits", + "housing_benefit_pre_benefit_cap", + ), + ) + sim.calculate(first, YEAR) + for name in ["universal_credit", "housing_benefit"]: + np.testing.assert_allclose( + sim.calculate(name, YEAR), + expected(standalone, name), + atol=0.01, + err_msg=name, + ) + + +def test_consistent_totals_without_housing_benefit_raise(system, standalone): + sim = make(system, consistent(standalone, "benefit_cap_welfare_benefits")) + with pytest.raises(ValueError, match="housing_benefit_pre_benefit_cap"): + sim.calculate("housing_benefit", YEAR) diff --git a/policyengine_uk/tests/test_non_dependant_spousal_maintenance.py b/policyengine_uk/tests/test_non_dependant_spousal_maintenance.py index e829efd5ef..ab7872153d 100644 --- a/policyengine_uk/tests/test_non_dependant_spousal_maintenance.py +++ b/policyengine_uk/tests/test_non_dependant_spousal_maintenance.py @@ -1,7 +1,8 @@ """Spousal maintenance is gross income for non-dependant deductions. -HB Regs 2006 reg 74(2), (9), pension-age HB reg 55(2), (10), and -SI 2012/2885 Sch 1 para 8(2), (9) do not disregard spousal maintenance. +HB Regs 2006 reg 74(2), (9), pension-age HB reg 55(2), (10), SI 2012/2885 +Sch 1 para 8(2), (9) and WSI 2013/3029 Sch 6 para 5(2), (9) do not disregard +spousal maintenance. It remains exempt from income tax under ITTOIA 2005 s 727. """ @@ -13,17 +14,18 @@ YEAR = 2026 -@pytest.fixture(scope="module") -def non_dependants(): +CASES = [ + ("earnings_only", 7_800, 0), + ("maintenance_100", 7_800, 5_200), + ("maintenance_150", 7_800, 7_800), + ("same_gross_earnings", 13_000, 0), +] + + +def build(country, local_authority): # Separate benefit units isolate each non-dependant's own gross income. - cases = [ - ("earnings_only", 7_800, 0), - ("maintenance_100", 7_800, 5_200), - ("maintenance_150", 7_800, 7_800), - ("same_gross_earnings", 13_000, 0), - ] people, benunits, households = {}, {}, {} - for name, earnings, maintenance in cases: + for name, earnings, maintenance in CASES: people[name] = { "age": {YEAR: 30}, "weekly_hours": {YEAR: 16}, @@ -35,18 +37,30 @@ def non_dependants(): benunits[name] = { "members": [name], "universal_credit": {YEAR: 0}, + "universal_credit_pre_benefit_cap": {YEAR: 0}, "pension_credit": {YEAR: 0}, } households[name] = { "members": [name], - "country": {YEAR: "ENGLAND"}, - "local_authority": {YEAR: "MERTON"}, + "country": {YEAR: country}, + "local_authority": {YEAR: local_authority}, } return Simulation( situation={"people": people, "benunits": benunits, "households": households} ) +@pytest.fixture(scope="module") +def non_dependants(): + return build("ENGLAND", "MERTON") + + +@pytest.fixture(scope="module") +def welsh_non_dependants(): + # Wales's national scheme covers working-age applicants too. + return build("WALES", "CARDIFF") + + def values(simulation, variable): return np.asarray(simulation.calculate(variable, YEAR), dtype=float) @@ -62,16 +76,19 @@ def test_housing_benefit_non_dependant_band_counts_spousal_maintenance(non_depen ) -def test_national_ctr_non_dependant_band_counts_spousal_maintenance(non_dependants): - # SI 2012/2885 Sch 1 para 8(2), (9): £150 earnings plus £100 - # maintenance and £250 earnings have the same normal gross weekly income. - # Compare bands without endorsing the pre-existing shared HB rate scale - # in the national CTR formula (#2007). - deductions = values( - non_dependants, "council_tax_reduction_individual_non_dep_deduction" +def test_national_ctr_non_dependant_band_counts_spousal_maintenance( + welsh_non_dependants, +): + # WSI 2013/3029 Sch 6 para 5(2), (9), as up-rated by WSI 2026/13: £150 + # earnings plus £100 maintenance and £250 earnings both fall below £279 + # (£6.70 a week); £150 plus £150 = £300 falls in £279–484.99 (£13.35). + np.testing.assert_allclose( + values( + welsh_non_dependants, "council_tax_reduction_individual_non_dep_deduction" + ), + [348.40, 348.40, 694.20, 348.40], + atol=0.01, ) - assert deductions[1] == pytest.approx(deductions[3], abs=0.01) - assert deductions[1] > deductions[0] def test_local_ctr_non_dependant_band_counts_spousal_maintenance(non_dependants): diff --git a/policyengine_uk/tests/test_rent_of_sharers_boarders_and_lodgers_properties.py b/policyengine_uk/tests/test_rent_of_sharers_boarders_and_lodgers_properties.py index 0b68d8b3b1..182638fbd8 100644 --- a/policyengine_uk/tests/test_rent_of_sharers_boarders_and_lodgers_properties.py +++ b/policyengine_uk/tests/test_rent_of_sharers_boarders_and_lodgers_properties.py @@ -19,10 +19,16 @@ most one family per household counts the household's non-dependants for Universal Credit, it is liable for the household's rent, and only it has Universal Credit deductions; Housing Benefit deductions over the families - liable for the rent sum to the household's non-dependants' deductions; a + liable for the rent sum to the household's non-dependant deductions (one + per couple, the higher: HB Regs 2006 reg 74(3)) times the share of the + rent borne by families whose claimant or partner is not exempt (reg + 74(6)), and never exceed the sum of individual deductions, with a sharer + never a non-dependant, even where the rent is nil (reg 3(2)(d)); a boarder's or lodger's family has no Universal Credit or Housing Benefit deductions; no sharer, boarder or lodger is a Council Tax Reduction non-dependant. + (The generated families have no non-dependants within their own + benefit unit, which test_benefit_unit_non_dependant_properties covers.) 5. Category: the Universal Credit LHA category does not depend on the household input is_shared_accommodation; the Housing Benefit category is the shared rate for any family entitled to one bedroom that lacks @@ -277,12 +283,31 @@ def test_non_dependants(population): assert np.all(per_household(sim, uc) <= total + 0.01) # Housing Benefit: apportioned over the families liable for the rent. hb = calc(sim, "housing_benefit_non_dep_deductions") - hb_individual = ( - calc(sim, "household_benefits_individual_non_dep_deduction") * non_dependant + # A sharer's family is a joint occupier, never a non-dependant (HB Regs + # 2006 reg 3(2)(d)), even where the rent is nil and so the family is not + # benunit_is_rent_liable. + sharer = sim.map_result((roles == "sharer").astype(float), "benunit", "person") > 0 + hb_individual = calc(sim, "household_benefits_individual_non_dep_deduction") * ( + non_dependant & ~sharer ) hb_total = sim.map_result(hb_individual, "person", "household") assert hb_total.max() > 0 - assert np.allclose(per_household(sim, hb), hb_total, rtol=1e-5, atol=0.01) + # The council deducts once for each non-dependant couple, the higher amount + # (HB Regs 2006 reg 74(3)), and once for each other member. The pool is + # apportioned over the families liable for the rent, and none falls on a + # family whose claimant or partner is exempt (reg 74(6)). + families = sim.populations["benunit"] + couple = calc(sim, "is_claimant_or_partner").astype(bool) + family_amount = families.max(hb_individual * couple) + families.sum( + hb_individual * ~couple + ) + pool = per_household(sim, family_amount) + exempt = calc(sim, "housing_benefit_non_dep_deductions_claimant_exempt").astype( + bool + ) + borne = per_household(sim, calc(sim, "share_of_household_rent") * ~exempt) + assert np.allclose(per_household(sim, hb), pool * borne, rtol=1e-5, atol=0.01) + assert np.all(per_household(sim, hb) <= hb_total + 0.01) assert np.all(hb[payer] == 0) assert np.all(uc[payer] == 0) # Council Tax Reduction: sharers, boarders and lodgers are not diff --git a/policyengine_uk/utils/benefit_cap.py b/policyengine_uk/utils/benefit_cap.py index 9a334e3ddf..e91e2fa09c 100644 --- a/policyengine_uk/utils/benefit_cap.py +++ b/policyengine_uk/utils/benefit_cap.py @@ -39,12 +39,12 @@ def benefit_cap_couple(benunit, period): (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). 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. + partner (is_claimant_or_partner; HB Regs 2006 regs 2(1), 75A). Both caps + count this couple's welfare benefits (benefit_cap_welfare_benefits, and + uc_benefit_cap_welfare_benefits without Housing Benefit). Universal Credit + alone caps a family receiving both awards (UC Regs 2013 reg 80(2A); HB + Regs 2006 reg 75F(1)(g)), so the Universal Credit couple counts. The two + couples match unless the inputs set is_uc_claimant. """ person = benunit.members on_universal_credit = benunit("universal_credit_pre_benefit_cap", period) > 0 diff --git a/policyengine_uk/utils/supplied_inputs.py b/policyengine_uk/utils/supplied_inputs.py index 38897f81bf..a6b71589fd 100644 --- a/policyengine_uk/utils/supplied_inputs.py +++ b/policyengine_uk/utils/supplied_inputs.py @@ -46,6 +46,7 @@ SUPPLIED_INPUT_VARIABLES = frozenset( { "adjusted_net_income", + "benefit_cap_welfare_benefits", "carers_allowance", "carers_allowance_pre_couple_limit", "carers_allowance_pre_overlap", @@ -67,6 +68,7 @@ "housing_benefit_applicable_income_childcare_element", "housing_benefit_applicable_income_disregard", "housing_benefit_childcare_earnings_limit", + "housing_benefit_pre_benefit_cap", "housing_benefit_specified_or_temporary_accommodation_disregard", "in_HE", "other_local_bus_trips", diff --git a/policyengine_uk/variables/gov/dwp/LHA_allowed_bedrooms.py b/policyengine_uk/variables/gov/dwp/LHA_allowed_bedrooms.py index 4c87a0df20..a2ae0337f7 100644 --- a/policyengine_uk/variables/gov/dwp/LHA_allowed_bedrooms.py +++ b/policyengine_uk/variables/gov/dwp/LHA_allowed_bedrooms.py @@ -43,8 +43,21 @@ class LHA_allowed_bedrooms(Variable): label = "The number of bedrooms covered by LHA for the benefit unit" documentation = ( "Universal Credit size criteria: one bedroom for the renter or " - "couple, one for each non-dependant aged 16 or over, and the " - "children's bedrooms. Joint tenants and other sharers of the rent, " + "couple, one for each qualifying young person the renter is " + "responsible for, one for each other member of the benefit unit aged " + "16 or over who is not the claimant, partner or household head (see " + "is_benefit_unit_non_dependant_for_universal_credit, whose members " + "also bear a housing cost contribution), one for each non-dependant " + "aged 16 or over from another family, one for each such member of a " + "family that makes no Universal Credit claim of its own (in the claim " + "that counts the household's non-dependants, which also bears their " + "contribution), and the children's bedrooms. The formula treats the " + "household head as liable for the rent, so a head who does not claim is not a non-dependant " + "(para 9(2)(d)); a head who is a qualifying young person the renter is " + "responsible for keeps their bedroom. A qualifying " + "young person no one is responsible for, such as one looked after by " + "a local authority, is not a non-dependant and has no bedroom, in " + "this family or another. Joint tenants and other sharers of the rent, " "boarders and lodgers are not non-dependants, so they add no bedroom " "to anyone's entitlement; the household's non-dependants count in one " "Universal Credit claim only (see uc_non_dependants_counted)." @@ -53,15 +66,64 @@ class LHA_allowed_bedrooms(Variable): reference = ( "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/9", "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/10", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/4", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/5", ) def formula(benunit, period, parameters): person = benunit.members + # A child is a person under 16 (WRA 2012 s.40). aged_16_or_over = person("age", period) >= 16 - # UC Regs 2013 Sch 4 para 10(1)(c): a non-dependant who is not a child. - non_dependant = aged_16_or_over & person( - "is_non_dependant_of_household_head", period + family_member = aged_16_or_over & ~person("is_claimant_or_partner", period) + qualifying = person("is_qualifying_young_person_for_universal_credit", period) + responsible = person( + "is_child_or_qualifying_young_person_for_universal_credit", period + ) + # UC Regs 2013 Sch 4 para 9(2)(g): a qualifying young person for whom + # no one is responsible (reg 4(6), such as one looked after by a local + # authority) is not a non-dependant, in this family or another. + no_one_responsible = qualifying & ~responsible + # Para 10(1)(b): a qualifying young person for whom the renter is + # responsible (regs 4 and 5). + qualifying_young_person = family_member & responsible + # Para 10(1)(c) with para 9(2): any other member who is not a child + # is a non-dependant with a bedroom, unless they head the household. + # is_benefit_unit_non_dependant_for_universal_credit marks them, and + # uc_non_dep_deductions charges a housing cost contribution for the + # same people (para 13), so the room and the contribution follow one + # definition. It reads the household head (is_resolved_household_head, + # the householder in whose name the accommodation is rented) as liable + # to make payments on a commercial basis for their occupation, so para + # 9(2)(d) stops them being a non-dependant. A head who does not claim + # is not a renter either (paras 1(2) and 9(1)(a)). A head who is a + # qualifying young person the renter is responsible for keeps the + # para 10(1)(b) bedroom above (para 9(1)(b)). + family_non_dependant = person( + "is_benefit_unit_non_dependant_for_universal_credit", period + ) + family_rooms = benunit.sum(qualifying_young_person | family_non_dependant) + # Para 10(1)(c): a non-dependant from another family who is not a + # child. + other_family = person("is_non_dependant_of_household_head", period) + non_dependant = aged_16_or_over & ~no_one_responsible & other_family + # An in-unit non-dependant whose own family makes no Universal Credit + # claim counts in the claim that counts the household's other + # non-dependants (para 9(2)(f)), as in uc_non_dep_deductions, so that + # claim has their bedroom as well as their housing cost contribution. + # Where that claim is their own family's, family_rooms already holds + # the bedroom. + own_claim = ( + benunit("benunit_is_rent_liable", period) + & benunit("is_uc_eligible", period) + & benunit("claims_universal_credit", period) ) counted = benunit("uc_non_dependants_counted", period) - non_dependants = counted * benunit.max(person.household.sum(non_dependant)) - return 1 + non_dependants + bedrooms_for_children(benunit, period) + passed_on = ( + family_non_dependant & ~other_family & ~benunit.project(own_claim | counted) + ) + non_dependants = counted * benunit.max( + person.household.sum(non_dependant | passed_on) + ) + return ( + 1 + family_rooms + non_dependants + bedrooms_for_children(benunit, period) + ) diff --git a/policyengine_uk/variables/gov/dwp/benefit_cap_welfare_benefits.py b/policyengine_uk/variables/gov/dwp/benefit_cap_welfare_benefits.py index dc0af1081b..3533f15608 100644 --- a/policyengine_uk/variables/gov/dwp/benefit_cap_welfare_benefits.py +++ b/policyengine_uk/variables/gov/dwp/benefit_cap_welfare_benefits.py @@ -16,10 +16,10 @@ # 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 +# Benefit as nil (UC Regs 2013 reg. 80(2A)), so its cap reads +# uc_benefit_cap_welfare_benefits, these benefits without Housing Benefit. +# Universal Credit before the cap is in the total too; HB reg. 75F(1)(g) puts a +# family receiving it outside the Housing Benefit cap. 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 @@ -53,16 +53,24 @@ class benefit_cap_welfare_benefits(Variable): "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 " + "The two couples match unless the inputs set is_uc_claimant. 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. Widowed parent's allowance counts; maternity allowance " - "and the other bereavement benefits, also welfare benefits, are not " - "modelled here." + "housing_benefit_pre_benefit_cap). Universal Credit's cap reads " + "uc_benefit_cap_welfare_benefits, which totals the same benefits " + "with Housing Benefit at nil (UC Regs 2013 reg. 80(2A)). Inputs that " + "supply this total include Housing Benefit in it. They also supply " + "housing_benefit_pre_benefit_cap, which " + "uc_benefit_cap_welfare_benefits subtracts, or supply " + "uc_benefit_cap_welfare_benefits itself, with a value for every " + "benefit unit; otherwise the model raises ValueError when it " + "calculates Universal Credit's cap. A reform of this variable changes " + "the Housing Benefit cap. To change the Universal Credit cap's basket, " + "replace uc_benefit_cap_welfare_benefits. Widowed parent's allowance " + "counts. The model has no variable for maternity allowance or the " + "other bereavement benefits, which Parliament also lists as welfare " + "benefits (Welfare Reform Act 2012 s. 96(10))." ) definition_period = YEAR unit = GBP 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 780d17bc56..e6480cfb82 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 @@ -54,12 +54,8 @@ def formula(benunit, period, parameters): p.northern_ireland, p.great_britain, ) - # Universal Credit before the benefit cap and deductions: Universal - # Credit depends on Housing Benefit through the benefit cap - # (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. + # Read Universal Credit before the benefit cap and deductions: + # entitlement counts whether or not DWP pays it. on_universal_credit = benunit("is_uc_entitled", period) & universal_credit_limb on_legacy_benefit = benunit( "in_receipt_of_income_support_jsa_ib_or_esa_ir", period diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/_non_dependants.py b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/_non_dependants.py new file mode 100644 index 0000000000..c50885abd1 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/_non_dependants.py @@ -0,0 +1,111 @@ +"""Shared definitions for Housing Benefit and Council Tax Reduction +non-dependant deductions.""" + +from policyengine_uk.model_api import * + +# Family benefits counted in a couple's gross income, besides Universal Credit. +# They are awarded to the benefit unit, so the person-level gross income +# variables leave them out. Income-based JSA and income-related ESA count the +# claimant's and partner's award only: another member claims in their own +# right. +FAMILY_GROSS_INCOME_BENEFITS = [ + "child_tax_credit", + "working_tax_credit", + "child_benefit", + "income_support", + "claimant_or_partner_jsa_income", + "claimant_or_partner_esa_income", + "pension_credit", +] + + +def non_dependant_weekly_gross_income(person, period, parameters, own_gross_income): + """Calculate normal weekly gross income for the deduction bands. The council + bands a claimant or partner on the couple's joint income (HB reg 74(4); CTR + Sch 1 para 8(4)): each member's own gross income plus the family's tax credits, + child benefit, Income Support, income-based JSA, income-related ESA and + Pension Credit, and its Universal Credit award (universal_credit_award) + where either member claims it. The council bands each other member of the + benefit unit separately on their own income, adding Universal Credit + where that member claims it alone. own_gross_income names the programme's + person-level gross income variable. + """ + claimant_or_partner = person("is_claimant_or_partner", period) + own_income = person(own_gross_income, period) + benunit = person.simulation.populations["benunit"] + other_awards = benunit.project(add(benunit, period, FAMILY_GROSS_INCOME_BENEFITS)) + # Universal Credit is the income of the claimants it assesses, the + # claimant and partner unless is_uc_claimant names someone else. + universal_credit = benunit.project(universal_credit_award(benunit, period)) + uc_claimant = person("is_uc_assessed_claimant", period) + couple_claims_universal_credit = person.benunit.any( + uc_claimant & claimant_or_partner + ) + couple_income = ( + person.benunit.sum(own_income * claimant_or_partner) + + other_awards + + where(couple_claims_universal_credit, universal_credit, 0) + ) + single_income = own_income + where( + uc_claimant & ~couple_claims_universal_credit, universal_credit, 0 + ) + annual = where(claimant_or_partner, couple_income, single_income) + return annual / WEEKS_IN_YEAR + + +def universal_credit_award(benunit, period): + """Return a family's Universal Credit award after the benefit cap (UC + Regs 2013 reg 81) and before debt deductions. Welfare Reform Act 2012 + s.8(1) sets the award as the maximum amount less the amounts for + income. DWP then takes advances (SI 2013/383 reg 10), overpayments (SI + 2013/384 regs 10-11) and third-party payments (SI 2013/380 Sch 6 para + 2(1)) from the sum payable, so those deductions stay the family's + income. A nil award carries none.""" + universal_credit = benunit("universal_credit", period) + deductions = benunit("uc_deductions", period) + return universal_credit + where(universal_credit > 0, deductions, 0) + + +def has_earned_income(person, period): + """Read whether Universal Credit counts positive earned income for the + person (uc_individual_earned_income). HB Regs 2006 reg 74(8)(b), (11) and + the CTR schemes' equivalents give "earned income" the meaning in UC Regs + 2013 reg 52. UC deducts allowable expenses, tax, National Insurance and + relievable pension contributions (reg 55(3), (5)); attributes company + income to its owner (reg 77(3)); offsets trading losses (reg 57(2)); and + applies the floor under reg 62, a couple's floor included.""" + return person("uc_individual_earned_income", period) > 0 + + +def is_on_legacy_income_related_benefit(person, period): + """Whether the person is on Income Support, income-based JSA or + income-related ESA: the award is payable to them (HB Regs 2006 reg 2(1), + (3) and (3A); the Council Tax Reduction schemes use the same + definitions).""" + return ( + person("is_on_income_support", period) + | person("is_on_income_based_jsa", period) + | person("is_on_income_related_esa", period) + ) + + +def deduction_per_family(benunit, period, deductions, both_members_of_couple): + """One deduction for the claimant and partner, the higher of theirs (HB reg + 74(3); CTR Sch 1 para 8(3)), or both where both_members_of_couple; plus a + separate deduction for each other member.""" + claimant_or_partner = benunit.members("is_claimant_or_partner", period) + couple = where( + both_members_of_couple, + benunit.sum(deductions * claimant_or_partner), + benunit.max(deductions * claimant_or_partner), + ) + return couple + benunit.sum(deductions * ~claimant_or_partner) + + +def charged_to_other_families(benunit, period, deduction_for_benunit): + """Deductions for the non-dependant families in the household other than + this one: each family's amount counted once (via its head).""" + is_benunit_head = benunit.members("is_benunit_head", period) + counted = is_benunit_head * benunit.project(deduction_for_benunit) + in_household = benunit.max(benunit.members.household.sum(counted)) + return in_household - deduction_for_benunit diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/household_benefits_individual_non_dep_deduction.py b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/household_benefits_individual_non_dep_deduction.py index 2bec92df06..162816d04f 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/household_benefits_individual_non_dep_deduction.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/household_benefits_individual_non_dep_deduction.py @@ -1,10 +1,22 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.dwp.housing_benefit.non_dep_deduction._non_dependants import ( + is_on_legacy_income_related_benefit, + non_dependant_weekly_gross_income, +) class household_benefits_individual_non_dep_deduction(Variable): value_type = float entity = Person label = "Housing Benefit individual non-dependent deduction" + documentation = ( + "For a non-dependant in remunerative work, the council deducts the amount for the band " + "containing their normal gross weekly income (a couple's joint income " + "for a claimant or partner of the non-dependant's family); bands " + "include their lower edge. For a non-dependant outside remunerative work, " + "including one on Income Support, income-based JSA or income-related " + "ESA (reg 6(6)), the council deducts the lowest amount." + ) definition_period = YEAR unit = GBP defined_for = "housing_benefit_individual_non_dep_deduction_eligible" @@ -17,32 +29,19 @@ class household_benefits_individual_non_dep_deduction(Variable): def formula(person, period, parameters): p = parameters(period).gov.dwp.housing_benefit.non_dep_deduction - income = person("housing_benefit_non_dep_gross_income", period) - weekly_income = income / WEEKS_IN_YEAR - on_income_related_benefit = ( - person("is_on_income_support", period) - | person("is_on_income_based_jsa", period) - | person("is_on_income_related_esa", period) + weekly_income = non_dependant_weekly_gross_income( + person, period, parameters, "housing_benefit_non_dep_gross_income" ) + # Reg 6(6), or pension-age reg 6(6): a person on Income Support, + # income-based JSA or income-related ESA is not in remunerative work. in_remunerative_work = ( person("weekly_hours", period) >= p.remunerative_work_hours - ) & ~on_income_related_benefit - banded = p.amount.calc(weekly_income, right=True) - # Reg 74(1)(b), or pension-age reg 55(1)(b), gives a non-dependant - # outside remunerative work the flat amount, the scale's first, - # instead of an income band. Reg 6(6) excludes income-related - # recipients from remunerative work. The gate only lowers a - # deduction: a non-worker with no banding income keeps the scale's - # zero, because the exemptions in reg 74(6), (7), (8) and (10), - # and reg 55(6)-(9) (a claimant on Attendance Allowance; a - # non-dependant on Pension Credit, or under 25 on Income Support; - # a full-time student) are not modelled, and the income-related - # benefits are not banding income, so many such non-dependants - # have none. - # #2007 adds the exemptions and the flat charge together. - deduction = where( - in_remunerative_work, - banded, - min_(banded, p.amount.amounts[0]), - ) - return deduction * WEEKS_IN_YEAR + ) & ~is_on_legacy_income_related_benefit(person, period) + # Reg 74(2), or pension-age reg 55(2): each band runs from "not less + # than" its lower edge. Reg 74(1)(b), or reg 55(1)(b): for a non-dependant + # outside remunerative work the council deducts the scale's lowest + # amount whatever their income. + banded = p.amount.calc(weekly_income) + not_in_remunerative_work = p.amount.calc(np.zeros_like(weekly_income)) + weekly = where(in_remunerative_work, banded, not_in_remunerative_work) + return weekly * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_individual_non_dep_deduction_eligible.py b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_individual_non_dep_deduction_eligible.py index 37ea75a311..be4b9b7810 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_individual_non_dep_deduction_eligible.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_individual_non_dep_deduction_eligible.py @@ -5,10 +5,27 @@ class housing_benefit_individual_non_dep_deduction_eligible(Variable): value_type = bool entity = Person label = "eligible person for the Housing Benefit non-dependent deduction" + documentation = ( + "A non-dependant aged 18 or over: either a member of another family " + "who is not liable for rent (see is_non_dependant_of_household_head) " + "or a member of their own benefit unit who is not the claimant, " + "partner or a child or young person (see " + "is_benefit_unit_non_dependant_for_legacy_benefits), who is not " + "exempt (housing_benefit_non_dep_deduction_exempt)." + ) definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/3", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/74", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/55", + ) def formula(person, period, parameters): - rent_liable = person.benunit("benunit_is_rent_liable", period) p = parameters(period).gov.dwp.housing_benefit.non_dep_deduction + non_dependant = person("is_non_dependant_of_household_head", period) | person( + "is_benefit_unit_non_dependant_for_legacy_benefits", period + ) age_eligible = person("age", period) >= p.age_threshold - return age_eligible & ~rent_liable + # An exempt non-dependant attracts no deduction (reg 74(7)-(10)). + exempt = person("housing_benefit_non_dep_deduction_exempt", period) + return non_dependant & age_eligible & ~exempt diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deduction_exempt.py b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deduction_exempt.py new file mode 100644 index 0000000000..f653095d4a --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deduction_exempt.py @@ -0,0 +1,60 @@ +from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.dwp.housing_benefit.non_dep_deduction._non_dependants import ( + has_earned_income, +) +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction._legacy import ( + is_full_time_student_non_dep, +) + + +class housing_benefit_non_dep_deduction_exempt(Variable): + value_type = bool + entity = Person + label = "Exempt from Housing Benefit non-dependant deductions" + documentation = ( + "Modelled exemptions: a full-time student (SI 2006/213 reg 74(7)(c)-(e); " + "SI 2006/214 reg 55(7)(e)); a non-dependant on State Pension Credit " + "(reg 74(10); reg 55(9)); and a non-dependant under 25 on Income " + "Support or income-based JSA, or entitled to Universal Credit " + "calculated on no earned income (reg 74(8); reg 55(8)). Not modelled: " + "a working student's summer vacation (reg 74(7)(d)), under-25s on " + "assessment-phase income-related ESA (no ESA group input), youth " + "training allowances, a normal home elsewhere, and absence in hospital, " + "prison or on armed forces operations." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/74", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/55", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.dwp.housing_benefit.non_dep_deduction + full_time_student = is_full_time_student_non_dep(person, period) + # IS, income-based JSA and SPC count for the person they are payable + # to (reg 2(1), (3)); Universal Credit for both joint claimants. + on_pension_credit = person("is_on_pension_credit", period) + on_legacy_income_related_benefit = person( + "is_on_income_support", period + ) | person("is_on_income_based_jsa", period) + # Read entitlement before the cap and debt deductions for the claimants + # the award assesses. DWP can reduce the payment to nil while + # entitlement still qualifies for the exemption. + entitled_to_universal_credit = person("is_uc_assessed_claimant", period) & ( + person.benunit("universal_credit_pre_benefit_cap", period) > 0 + ) + universal_credit_without_earned_income = ( + entitled_to_universal_credit & ~has_earned_income(person, period) + ) + under_age_limit = person("age", period) < p.income_related_benefit_age_limit + return ( + full_time_student + | on_pension_credit + | ( + under_age_limit + & ( + on_legacy_income_related_benefit + | universal_credit_without_earned_income + ) + ) + ) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions.py b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions.py index e5e9e3a933..636946565e 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions.py @@ -1,30 +1,81 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.dwp.housing_benefit.non_dep_deduction._non_dependants import ( + deduction_per_family, +) class housing_benefit_non_dep_deductions(Variable): value_type = float entity = BenUnit label = "non-dependent deductions" + documentation = ( + "Deductions for the non-dependants residing with the claimant: " + "members of other families of the household who are not liable for " + "rent, and members of a family liable for the rent who are not its " + "claimant, partner or a child or young person. One deduction for " + "each non-dependant couple, the higher of the two members' amounts, " + "and one for each other non-dependant. Each is apportioned between " + "joint occupiers by their shares of the rent, whichever family they " + "are in; a boarder or lodger also bears the whole deduction for a " + "non-dependant in its own benefit unit. None if the claimant or " + "partner is exempt." + ) definition_period = YEAR unit = GBP reference = ( "https://www.legislation.gov.uk/uksi/2006/213/regulation/3", "https://www.legislation.gov.uk/uksi/2006/213/regulation/74", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/3", "https://www.legislation.gov.uk/uksi/2006/214/regulation/55", + "https://assets.publishing.service.gov.uk/media/5a7ce2b840f0b6629523c64c/hbgm-a5-calculating-benefit.pdf", ) def formula(benunit, period, parameters): - # Deductions are made for non-dependants residing with the claimant + # The council deducts for non-dependants residing with the claimant # (HB Regs 2006 reg 74; HB (SPC) Regs 2006 reg 55). Joint occupiers, # boarders, lodgers and the landlord's household are not - # non-dependants (reg 3(2)(d)-(e), 3(4)). A non-dependant of more - # than one joint occupier is apportioned between them by their shares - # of the payments (reg 74(5); SPC reg 55(5)), so each family liable - # for the household's rent bears its share; a boarder or lodger bears - # none. + # non-dependants (reg 3(2)(d)-(e), 3(4)). For a couple the council deducts once, + # the higher amount (reg 74(3)). For a non-dependant of more than one joint + # occupier, the council apportions the deduction by their shares of the + # payments (reg 74(5); SPC reg 55(5)), so each family liable for the + # household's rent bears its share; a boarder or lodger bears none of it. + # A family bearing a share is a joint occupier, never another family's + # non-dependant, even where the rent is nil and so the family is not + # benunit_is_rent_liable. person = benunit.members - deductions = person( - "household_benefits_individual_non_dep_deduction", period - ) * person("is_non_dependant_of_household_head", period) + deductions = person("household_benefits_individual_non_dep_deduction", period) + in_joint_occupier_family = person.benunit("share_of_household_rent", period) > 0 + other_family = ( + person("is_non_dependant_of_household_head", period) + & ~in_joint_occupier_family + ) + # A member of a rent-liable family who is not in its claimant's + # family under reg 3(2)(a) and (c) is a non-dependant too. + own_family = ( + person("is_benefit_unit_non_dependant_for_legacy_benefits", period) + & ~other_family + ) + # A non-dependant normally resides with each joint occupier (reg 3(1) + # and (4)), whichever family they belong to: another family's adult, + # a joint occupier's own adult son, or a boarder's or lodger's (reg + # 3(2)(e)(i) excludes only the person liable to pay the claimant). + # A couple pays one deduction, the higher (reg 74(3)), and each other + # non-dependant separately, counted once per family. One who is a + # non-dependant of more than one joint occupier is apportioned + # between them by their shares of the payments (reg 74(5); SPC reg + # 55(5)). + family_amount = deduction_per_family( + benunit, period, deductions * (other_family | own_family), False + ) + counted = person("is_benunit_head", period) * benunit.project(family_amount) share = benunit("share_of_household_rent", period) - return share * benunit.max(person.household.sum(deductions)) + from_joint_occupiers = share * benunit.max(person.household.sum(counted)) + # A boarder or lodger is not a joint occupier with the householder, + # so it also bears the whole deduction for a non-dependant in its own + # benefit unit. The model charges it for no one else. + joint_occupier = share > 0 + from_own_family = where(joint_occupier, 0, benunit.sum(deductions * own_family)) + claimant_exempt = benunit( + "housing_benefit_non_dep_deductions_claimant_exempt", period + ) + return where(claimant_exempt, 0, from_joint_occupiers + from_own_family) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions_claimant_exempt.py b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions_claimant_exempt.py new file mode 100644 index 0000000000..b8ed5eb93f --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_deductions_claimant_exempt.py @@ -0,0 +1,32 @@ +from policyengine_uk.model_api import * + + +class housing_benefit_non_dep_deductions_claimant_exempt(Variable): + value_type = bool + entity = BenUnit + label = "Exempt from all Housing Benefit non-dependant deductions" + documentation = ( + "The council makes no deduction for any non-dependant if the claimant or partner " + "is blind or receives Attendance Allowance, the DLA care component, the " + "PIP daily living component or Armed Forces Independence Payment (SI " + "2006/213 reg 74(6); SI 2006/214 reg 55(6)). Scottish disability " + "assistance (pension age and adult disability payments, child " + "disability payment, Scottish adult DLA) has no separate input." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/74", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/55", + ) + + def formula(benunit, period, parameters): + person = benunit.members + claimant_or_partner = person("is_claimant_or_partner", period) + qualifying = ( + person("is_blind", period) + | (person("attendance_allowance", period) > 0) + | (person("dla_sc", period) > 0) + | (person("pip_dl", period) > 0) + | (person("armed_forces_independence_payment", period) > 0) + ) + return benunit.any(claimant_or_partner & qualifying) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.py b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.py index 527588841d..0faeda2b2a 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/non_dep_deduction/housing_benefit_non_dep_gross_income.py @@ -16,27 +16,29 @@ class housing_benefit_non_dep_gross_income(Variable): "https://www.legislation.gov.uk/uksi/2006/214/regulation/55", ) documentation = ( - "Annual gross income represented by the model for non-dependant " + "Annual gross income the model represents for the non-dependant " "bands, before tax, National Insurance, employment deductions and " - "pension contributions. Signed trading/property inputs are retained; " - "a separate cross-source loss-offset rule is not modelled. Claimant " - "war-pension and supplementary-pension disregards do not apply to " - "these non-dependant bands. It includes statutory sick, maternity and " - "paternity pay once through employment_benefits, child and spousal " - "maintenance, the tax-purpose social security income sources, and " - "the separately represented non-taxable periodic benefits. " - "Attendance Allowance, DLA and PIP are omitted under the specific " - "disability-payment disregards. The other disregarded disability " - "and compensation/trust payments have no separate model inputs. " - "Benefit-unit awards (including Child Benefit, tax credits and UC) " - "are not allocated to an individual recipient here; adding computed " - "UC would also create a cycle through the benefit cap and HB. " - "BSP and AFCS inputs do not separate periodic income from capital " - "payments, so they are not added. Other investment income remains " - "excluded to preserve this income base; extending it remains for " - "#2007. The deduction formula applies the remunerative-work gate. The " - "gross-income variable itself does not decide whether someone is " - "in remunerative work or whether a deduction is due." + "pension contributions. DWP tells councils to count earnings and any " + "other income (HB guidance A5.542), to count a self-employed " + "non-dependant's total income before business expenses (A5.551) and " + "to add savings interest to earnings (A5.570). The model holds " + "self-employment and property profits rather than receipts, so it " + "adds each profit and counts a loss as nil, and a loss never reduces " + "other income. The claimant war-pension and supplementary-pension " + "disregards do not apply to these bands. The total includes " + "statutory sick, maternity and paternity pay once, through " + "employment_benefits; child and spousal maintenance; the tax-purpose " + "social security income sources; the non-taxable periodic benefits " + "the model represents separately; and other investment income, ISA " + "interest (tax_free_savings_income) and ISA dividends. It leaves out " + "Attendance Allowance, DLA and PIP under the disability-payment " + "disregards; the other disregarded disability, compensation and " + "trust payments have no model inputs. BSP and AFCS inputs do not " + "separate periodic income from capital payments, so the total leaves " + "them out. Benefit-unit awards, such as Child Benefit, tax credits " + "and Universal Credit, stay out of it. The deduction formula adds " + "family awards to the claimant and partner's joint income and applies " + "the remunerative-work test." ) def formula(person, period, parameters): @@ -48,10 +50,13 @@ def formula(person, period, parameters): "employment_benefits", "private_pension_income", "social_security_income", - "self_employment_income", - "property_income", "savings_interest_income", + # Count ISA interest unless the inputs supply an exempt-interest + # total. + "tax_free_savings_income", + "other_investment_income", "dividend_income", + "individual_savings_account_dividend_income", "miscellaneous_income_before_trading_allowance", "maternity_allowance", "iidb", @@ -59,8 +64,15 @@ def formula(person, period, parameters): "war_widows_pension", ], ) + # The model has profits, not the receipts DWP HB guidance A5.551 + # counts before business expenses. As an approximation, a loss + # counts as nil, so it never reduces other income. + losses_as_nil = max_(0, person("self_employment_income", period)) + max_( + 0, person("property_income", period) + ) return ( income + + losses_as_nil + max_(0, person("maintenance_income", period)) + max_(0, person("spousal_maintenance_income", period)) ) diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_unit_non_dependant_for_legacy_benefits.py b/policyengine_uk/variables/gov/dwp/is_benefit_unit_non_dependant_for_legacy_benefits.py new file mode 100644 index 0000000000..7069ff4524 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_benefit_unit_non_dependant_for_legacy_benefits.py @@ -0,0 +1,54 @@ +from policyengine_uk.model_api import * + + +class is_benefit_unit_non_dependant_for_legacy_benefits(Variable): + value_type = bool + entity = Person + label = "Non-dependant within the benefit unit for Housing Benefit and Council Tax Reduction" + documentation = ( + "A member of the benefit unit who is neither the claimant or partner " + "nor a child or young person: someone aged 16 or over who is not a " + "Child Benefit qualifying young person. The benefit unit follows the " + "survey's family, which can include an adult child or a 16 to 19 year " + "old who is not a qualifying young person. Under the Housing Benefit " + "and Council Tax Reduction schemes such a person is not in the " + "claimant's family (SSCBA 1992 s.137(1), with young person defined " + "by Child Benefit) but normally resides with the claimant, so is a " + "non-dependant. A child or young person is either in the family or, " + "if placed with the claimant by a local authority, excluded from " + "being a non-dependant. Such a person's own benefit awards are not " + "modelled, so the exemptions for a non-dependant on an income-related " + "benefit or on Universal Credit with no earned income (HB reg 74(8), " + "CTR Sch 1 para 8(8)) never apply to them. The household head " + "(is_resolved_household_head), a householder in whose name the " + "accommodation is rented, is liable for it and so never a " + "non-dependant (HB reg 3(2)(d)-(e)), even where the benefit unit's " + "claimant and partner are someone else. Council Tax Reduction uses " + "is_benefit_unit_non_dependant_for_council_tax_reduction, built on " + "the CTR applicant." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/3", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/19", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/21", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/3", + "https://www.legislation.gov.uk/uksi/2012/2885/regulation/9", + "https://www.legislation.gov.uk/wsi/2013/3029/regulation/9", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/8", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/3", + "https://www.legislation.gov.uk/ukpga/1992/4/section/137", + ) + + def formula(person, period, parameters): + # HB Regs 2006 and HB (SPC) Regs 2006 reg 3(2)(a), (c) with regs 19 + # and 21(3); SI 2012/2885 and WSI 2013/3029 reg 9(2)(a), (c); SSI + # 2021/249 reg 8(2)(a)-(b); SSI 2012/319 reg 3. A Child Benefit child + # is under 16. + claimant_or_partner = person("is_claimant_or_partner", period) + child_or_young_person = person( + "is_child_or_qualifying_young_person_for_child_benefit", period + ) + # Reg 3(2)(d)-(e): the household head is liable for the dwelling. + household_head = person("is_resolved_household_head", period) + return ~claimant_or_partner & ~child_or_young_person & ~household_head diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_unit_non_dependant_for_universal_credit.py b/policyengine_uk/variables/gov/dwp/is_benefit_unit_non_dependant_for_universal_credit.py new file mode 100644 index 0000000000..80dcda54e2 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_benefit_unit_non_dependant_for_universal_credit.py @@ -0,0 +1,47 @@ +from policyengine_uk.model_api import * + + +class is_benefit_unit_non_dependant_for_universal_credit(Variable): + value_type = bool + entity = Person + label = "Non-dependant within the benefit unit for Universal Credit" + documentation = ( + "A member of the benefit unit, aged 16 or over, who is neither the " + "claimant or partner nor a qualifying young person. The benefit unit " + "follows the survey's family, which can include an adult child or a " + "16 to 19 year old who is not a qualifying young person. In Universal " + "Credit law such a person is not in the claimant's family but is a " + "non-dependant of the renter: they normally live in the accommodation " + "and are none of the persons excluded by Sch 4 para 9(2). A qualifying " + "young person the renter is responsible for is a member of the " + "renter's extended benefit unit, and one no one is responsible for, " + "for example one looked after by a local authority, is excluded by " + "para 9(2)(g). A child, under 16, is always one or the other. The " + "household head (is_resolved_household_head), a householder in whose " + "name the accommodation is rented, is liable for it and so never a " + "non-dependant (para 9(2)(d)), even where the benefit unit's " + "claimant and partner are someone else." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/9", + "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/ukpga/2012/5/section/40", + ) + + def formula(person, period, parameters): + # UC Regs 2013 Sch 4 para 9(1)(a)-(b) and (2)(a), (g); WRA 2012 s.40. + not_a_child = ~person("is_child_for_universal_credit", period) + claimant_or_partner = person("is_claimant_or_partner", period) + qualifying_young_person = person( + "is_qualifying_young_person_for_universal_credit", period + ) + # Para 9(2)(d): the household head is liable for the accommodation. + household_head = person("is_resolved_household_head", period) + return ( + not_a_child + & ~claimant_or_partner + & ~qualifying_young_person + & ~household_head + ) 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 index eacb266686..3571e6fb82 100644 --- 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 @@ -29,11 +29,10 @@ class uc_benefit_cap_reduction(Variable): 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 - ) + # for the Housing Benefit cap (HB Regs 2006 reg 75C(2)(b)); + # uc_benefit_cap_welfare_benefits leaves it out, so Universal Credit + # caps a family paid both on its other benefits. + welfare_benefits = benunit("uc_benefit_cap_welfare_benefits", 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 diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/uc_benefit_cap_welfare_benefits.py b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/uc_benefit_cap_welfare_benefits.py new file mode 100644 index 0000000000..d373a2387c --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/benefit_cap/uc_benefit_cap_welfare_benefits.py @@ -0,0 +1,104 @@ +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_couple +from policyengine_uk.utils.supplied_inputs import supplied_input +from policyengine_uk.variables.gov.dwp.benefit_cap_welfare_benefits import ( + CLAIMANT_OR_PARTNER_BENEFITS, + FAMILY_BENEFITS, +) +from policyengine_uk.variables.gov.dwp.esa_income import ( + income_related_esa_award_on_reports, +) +from policyengine_uk.variables.gov.dwp.jsa_income import ( + income_related_jsa_award_on_reports, +) + +# UC Regs 2013 reg 80(2A): where the welfare benefit is Housing Benefit, "the +# amount to be used is nil". The Universal Credit cap therefore counts the +# Housing Benefit cap's benefits other than Housing Benefit. +UC_FAMILY_BENEFITS = [ + benefit + for benefit in FAMILY_BENEFITS + if benefit != "housing_benefit_pre_benefit_cap" +] + + +def supplied_total_without_housing_benefit(period) -> str: + return ( + f"The inputs supply benefit_cap_welfare_benefits for {period}, a total " + "that includes Housing Benefit, which DWP counts as nil for the " + "Universal Credit cap (UC Regs 2013 reg. 80(2A)). Supply " + "housing_benefit_pre_benefit_cap for the same period, for the model to " + "subtract, or supply uc_benefit_cap_welfare_benefits, with a value for " + "every benefit unit: a situation that sets either for some benefit " + "units stores nil for the rest. The model doesn't calculate that " + "Housing Benefit for Universal Credit, because the Housing Benefit " + "non-dependant deductions read universal_credit. A reform that " + "neutralizes housing_benefit_pre_benefit_cap drops a supplied value; " + "supply uc_benefit_cap_welfare_benefits instead." + ) + + +class uc_benefit_cap_welfare_benefits(Variable): + value_type = float + entity = BenUnit + label = "Welfare benefits counted by the Universal Credit benefit cap" + documentation = ( + "The welfare benefits the Universal Credit cap counts: those in " + "benefit_cap_welfare_benefits, for the same single person or couple, " + "with Housing Benefit at nil (UC Regs 2013 reg. 80(2A)). Where the " + "inputs supply benefit_cap_welfare_benefits, whose total includes " + "Housing Benefit, this subtracts the supplied " + "housing_benefit_pre_benefit_cap. Inputs that supply the combined " + "total also supply housing_benefit_pre_benefit_cap or this variable, " + "with a value for every benefit unit; otherwise the formula raises " + "ValueError. No baseline formula on " + "Universal Credit's route calculates Housing Benefit, so the Housing " + "Benefit non-dependant deductions can read universal_credit." + ) + 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/79", + "https://www.legislation.gov.uk/uksi/2013/376/regulation/80", + ) + + def formula(benunit, period, parameters): + # A supplied combined total includes Housing Benefit, and the inputs + # supply that Housing Benefit too. Calculating it here would make + # Universal Credit depend on Housing Benefit, whose non-dependant + # deductions read universal_credit. A value a scenario stores as the + # model's own (set_model_value) supplies neither. + if supplied_input(benunit, "benefit_cap_welfare_benefits", period) is not None: + if ( + supplied_input(benunit, "housing_benefit_pre_benefit_cap", period) + is None + ): + raise ValueError(supplied_total_without_housing_benefit(period)) + return benunit("benefit_cap_welfare_benefits", period) - benunit( + "housing_benefit_pre_benefit_cap", period + ) + # The rest follows benefit_cap_welfare_benefits, in the same order. + couple = benefit_cap_couple(benunit, period) + income_based = award_of_couple( + benunit, + period, + "jsa_income", + "jsa_income_reported", + income_related_jsa_award_on_reports, + couple, + ) + award_of_couple( + benunit, + period, + "esa_income", + "esa_income_reported", + income_related_esa_award_on_reports, + couple, + ) + return income_based + add_for_members( + benunit, + period, + CLAIMANT_OR_PARTNER_BENEFITS + UC_FAMILY_BENEFITS, + couple, + ) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction_eligible.py b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction_eligible.py index abd9869256..d47928de45 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction_eligible.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_individual_non_dep_deduction_eligible.py @@ -5,14 +5,27 @@ class uc_individual_non_dep_deduction_eligible(Variable): value_type = bool entity = Person label = "Eligible person for the Universal Credit non-dependent deduction" + documentation = ( + "A non-dependant of a renter, aged 21 or over and not otherwise " + "exempt: either a member of another family who is not liable for rent " + "(see is_non_dependant_of_household_head) or a member of their own " + "benefit unit who is not the claimant, partner or a qualifying young " + "person (see is_benefit_unit_non_dependant_for_universal_credit)." + ) definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/9", + "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/13", + "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/16", + ) def formula(person, period, parameters): - not_rent_liable = ~person.benunit("benunit_is_rent_liable", period) p = parameters( period ).gov.dwp.universal_credit.elements.housing.non_dep_deduction - age = person("age", period) - age_eligible = age >= p.age_threshold + non_dependant = person("is_non_dependant_of_household_head", period) | person( + "is_benefit_unit_non_dependant_for_universal_credit", period + ) + age_eligible = person("age", period) >= p.age_threshold exempt = person("uc_non_dep_deduction_exempt", period) - return ~exempt & age_eligible & not_rent_liable + return non_dependant & age_eligible & ~exempt diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.py b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.py index be35b6197f..695a753059 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deduction_exempt.py @@ -16,7 +16,8 @@ class uc_non_dep_deduction_exempt(Variable): "with the claimant or partner; the existing Universal Credit child " "age measure excludes children looked after by a local authority. Underlying " "entitlement reduced to nil by overlapping benefits does not qualify " - "for the carer exemption." + "for the carer exemption. Pension Credit is awarded to a benefit unit's claimant and partner, " + "so another adult in the unit is not receiving it." ) reference = "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/16" @@ -32,7 +33,12 @@ def formula(person, period, parameters): < p.child_responsibility_age_limit ) return ( - person.benunit("uc_non_dep_receives_pension_credit", period) + # Pension Credit is the claimant's and partner's award, not that of + # another adult in their benefit unit (para 16(2)(b)). + ( + person("is_claimant_or_partner", period) + & person.benunit("uc_non_dep_receives_pension_credit", period) + ) | person("dla_sc_middle_plus", period) | (person("pip_dl", period) > 0) | (person("attendance_allowance", period) > 0) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deductions.py b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deductions.py index c09e43cc46..9f06dbb16d 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deductions.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deductions.py @@ -5,22 +5,59 @@ class uc_non_dep_deductions(Variable): value_type = float entity = BenUnit label = "Universal Credit non-dependent deductions" + documentation = ( + "Housing cost contributions for the renter's non-dependants. Those in " + "other families of the household count in one claim, the one " + "uc_non_dependants_counted picks (the household head's family if it " + "claims, otherwise the first claiming sharer). Those within a " + "renter's own benefit unit count in that family's claim if it makes " + "one, otherwise in the same counted claim. Making a claim is the " + "model's claims_universal_credit flag for a renting, UC-eligible " + "family, so a family flagged as claiming counts its own non-dependants " + "even where its award is nil. None if the renter or a joint renter is " + "exempt." + ) definition_period = YEAR unit = GBP reference = ( "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/9", "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/13", + "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/15", ) def formula(benunit, period, parameters): # A housing cost contribution is deducted for each non-dependant in # the renter's extended benefit unit (UC Regs 2013 Sch 4 para 13). - # Non-dependants are not liable for rent (para 9(2)(d)), and each - # counts in one Universal Credit claim only (para 9(2)(f)): see - # uc_non_dependants_counted. person = benunit.members - deductions = person("uc_individual_non_dep_deduction", period) * person( - "is_non_dependant_of_household_head", period + deductions = person("uc_individual_non_dep_deduction", period) + # Non-dependants from other families are not liable for rent (para + # 9(2)(d)), and each counts in one Universal Credit claim only (para + # 9(2)(f)): see uc_non_dependants_counted. + other_family = person("is_non_dependant_of_household_head", period) + # A member of a renter's own benefit unit who is not the claimant, + # partner or a qualifying young person is a non-dependant of that + # renter (para 9(1)(c) and (2)(a), (g)). Their own family's claim + # counts them if it makes one; otherwise the claim that counts the + # household's other non-dependants does (para 9(2)(f)). + in_unit = ( + person("is_benefit_unit_non_dependant_for_universal_credit", period) + & ~other_family + ) + own_claim = ( + benunit("benunit_is_rent_liable", period) + & benunit("is_uc_eligible", period) + & benunit("claims_universal_credit", period) ) + to_own_claim = in_unit & benunit.project(own_claim) counted = benunit("uc_non_dependants_counted", period) - return counted * benunit.max(person.household.sum(deductions)) + from_household = counted * benunit.max( + person.household.sum( + deductions * ((other_family | in_unit) & ~to_own_claim) + ) + ) + from_own_family = benunit.sum(deductions * to_own_claim) + # Sch 4 para 15: no contribution for any non-dependant where the + # renter, or either joint renter, is blind or receives a qualifying + # disability benefit. + renter_exempt = benunit("uc_non_dep_deductions_renter_exempt", period) + return where(renter_exempt, 0, from_household + from_own_family) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deductions_renter_exempt.py b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deductions_renter_exempt.py new file mode 100644 index 0000000000..187b0ec1ab --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/housing_costs_element/non_dep_deduction/uc_non_dep_deductions_renter_exempt.py @@ -0,0 +1,31 @@ +from policyengine_uk.model_api import * + + +class uc_non_dep_deductions_renter_exempt(Variable): + value_type = bool + entity = BenUnit + label = "Universal Credit renter exempt from all housing cost contributions" + documentation = ( + "No housing cost contribution is deducted for any non-dependant if the " + "renter, or either joint renter of a couple, is blind or receives " + "Attendance Allowance, the middle or highest rate of the DLA care " + "component or the PIP daily living component. Scottish disability " + "assistance (adult and pension age disability payments, child " + "disability payment, Scottish adult DLA) has no separate input, and " + "entitlement suspended in hospital is not modelled." + ) + definition_period = YEAR + reference = "https://www.legislation.gov.uk/uksi/2013/376/schedule/4/paragraph/15" + + def formula(benunit, period, parameters): + # UC Regs 2013 Sch 4 para 15: "joint renters" are joint claimants + # (para 1(2)), so the claimant and partner. + person = benunit.members + renter = person("is_claimant_or_partner", period) + qualifying = ( + person("is_blind", period) + | person("dla_sc_middle_plus", period) + | (person("attendance_allowance", period) > 0) + | (person("pip_dl", period) > 0) + ) + return benunit.any(renter & qualifying) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_legacy.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_legacy.py index 4383dba483..c4b5c6f541 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_legacy.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_legacy.py @@ -4,6 +4,21 @@ ) +def in_other_non_liable_family(person, period): + """Whether each person is outside every family claiming Council Tax + Reduction for the household, outside every family liable for a share of + its rent, and not paying the householder for their own occupation: all + such people aged 18 or over are the applicants' non-dependants (SI + 2012/2885 reg 9(2)(a), (d)-(e) and the Welsh and Scottish equivalents). + The eligibility for a non-dependant deduction and the local schemes' + aggregation both use this, so they stay on one definition.""" + return ( + ~person.benunit("council_tax_reduction_claimant_benunit", period) + & ~person.benunit("liable_for_share_of_household_rent", period) + & ~person("pays_rent_to_householder", period) + ) + + def is_full_time_student_non_dep(person, period): return (person("current_education", period) != EducationType.NOT_IN_EDUCATION) | ( person("in_HE", period) @@ -94,7 +109,14 @@ def local_non_dep_deductions( individual_deduction_variable, one_deduction_for_uc_couples=True, ): - deductions = benunit.members(individual_deduction_variable, period) + # The local schemes still charge only the non-dependants of other + # families. Non-dependants within a family's own benefit unit, now + # eligible for the national schemes, are left out here until each + # council's aggregation is brought into line. + person = benunit.members + deductions = person(individual_deduction_variable, period) * ( + in_other_non_liable_family(person, period) + ) deduction_for_benunit = benunit.max(deductions) if not one_deduction_for_uc_couples: has_uc = benunit("universal_credit", period) > 0 diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_national_non_dependants.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_national_non_dependants.py new file mode 100644 index 0000000000..e5bc21bb04 --- /dev/null +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_national_non_dependants.py @@ -0,0 +1,188 @@ +"""National Council Tax Reduction non-dependant deductions under a given +applicant's scheme. Each applicant's scheme follows their own family +(council_tax_reduction_pensioner), so a person's deduction can differ between +two applicants in one household: England deducts under its pensioner scheme +only (working-age applicants are in the local schemes), Wales deducts for both +members of a Universal Credit couple only from a working-age applicant (WSI +2013/3029 Sch 6 para 5(3)), and Scotland's partner rule applies only in its +working-age scheme (SSI 2021/249 reg 90(8)(a)).""" + +from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.dwp.housing_benefit.non_dep_deduction._non_dependants import ( + has_earned_income, + is_on_legacy_income_related_benefit, + non_dependant_weekly_gross_income, +) +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction._legacy import ( + is_full_time_student_non_dep, +) +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction.config import ( + is_england_pensioner_scheme, + is_scotland_scheme, + is_wales_scheme, +) + + +def household_head_family_is_pensioner(person, period): + """The scheme of the household head's family, which the person-level + variables report.""" + head_family = person.benunit("benunit_contains_household_head", period) + pensioner = person.benunit("council_tax_reduction_pensioner", period) + return person.household.any(head_family & pensioner) + + +def is_qualifying_income_related_benefit_claimant( + person, period, parameters, pensioner_applicant +): + """Whether the person is on Income Support, income-based JSA or + income-related ESA for a scheme's non-dependant rules, under the claim of + an applicant who is (pensioner_applicant) or is not a pensioner. Such a + person is not in remunerative work (SI 2012/2885 reg 10(6); WSI 2013/3029 + reg 10(6); SSI 2012/319 reg 6(6); SSI 2021/249 reg 10(6)) and has no + deduction (Sch 1 para 8(8)(a) and equivalents). In Scotland's working-age + scheme from April 2022 this is a "qualifying income-related benefit + claimant", defined in SSI 2021/249 reg 4(1) as one "who is, or who has a + partner who is," on one of those benefits "and is not on universal + credit". It is read for the non-dependant, as the definition's + "applicant" would leave reg 90(8)(a) no application.""" + scotland = parameters(period).gov.local_authorities.scotland.council_tax_reduction + country = person.household("country", period) + claimant_or_partner = person("is_claimant_or_partner", period) + on_benefit = is_on_legacy_income_related_benefit(person, period) + couple_on_benefit = claimant_or_partner & ( + person.benunit.any(on_benefit & claimant_or_partner) + ) + entitled_to_universal_credit = person("is_uc_assessed_claimant", period) & ( + person.benunit("universal_credit_pre_benefit_cap", period) > 0 + ) + partner_rule = ( + is_scotland_scheme(country) + & ~pensioner_applicant + & scotland.non_dep_deduction.working_age_exempt_partner_of_income_related_benefit_claimant + ) + return where( + partner_rule, + (on_benefit | couple_on_benefit) & ~entitled_to_universal_credit, + on_benefit, + ) + + +def non_dep_deduction_exempt(person, period, parameters, pensioner_applicant): + """Whether a national scheme makes no deduction for this person from the + claim of an applicant who is (pensioner_applicant) or is not a pensioner.""" + local_authorities = parameters(period).gov.local_authorities + england = local_authorities.england.council_tax_reduction.pensioners + scotland = local_authorities.scotland.council_tax_reduction + wales = local_authorities.wales.council_tax_reduction + country = person.household("country", period) + schemes = [ + is_england_pensioner_scheme(country, pensioner_applicant), + is_scotland_scheme(country), + is_wales_scheme(country), + ] + full_time_student = is_full_time_student_non_dep(person, period) + # IS, income-based JSA, income-related ESA and SPC count for the person + # they are payable to; Universal Credit for both joint claimants. + claimant_or_partner = person("is_claimant_or_partner", period) + on_pension_credit = person("is_on_pension_credit", period) + entitled_to_universal_credit = person("is_uc_assessed_claimant", period) & ( + person.benunit("universal_credit_pre_benefit_cap", period) > 0 + ) + qualifying_income_related_benefit_claimant = ( + is_qualifying_income_related_benefit_claimant( + person, period, parameters, pensioner_applicant + ) + ) + # LGFA 1992 Sch 1 para 3 (via para 8(8)(b) and equivalents): an adult + # "in respect of whom another person is entitled to child benefit". + # Entitlement needs a claim (SSAA 1992 s.1(1)); an election not to be + # paid keeps it (s.13A), so the opt-out flag is not read. + entitled_to_child_benefit_for = ( + add(person, period, ["child_benefit_respective_amount"]) > 0 + ) & person.benunit("would_claim_child_benefit", period) + disregarded_for_child_benefit = ( + (person("age", period) >= 18) + & entitled_to_child_benefit_for + & ~claimant_or_partner + ) + universal_credit_limb = select( + schemes, + [ + england.non_dep_deduction.exempt_universal_credit_without_earned_income, + scotland.non_dep_deduction.exempt_universal_credit_without_earned_income, + wales.non_dep_deduction.exempt_universal_credit_without_earned_income, + ], + default=False, + ) + universal_credit_without_earned_income = ( + universal_credit_limb + & entitled_to_universal_credit + & ~has_earned_income(person, period) + ) + return ( + full_time_student + | qualifying_income_related_benefit_claimant + | on_pension_credit + | disregarded_for_child_benefit + | universal_credit_without_earned_income + ) + + +def individual_non_dep_deduction(person, period, parameters, pensioner_applicant): + """A person's annual deduction from the claim of an applicant who is + (pensioner_applicant) or is not a pensioner, before eligibility. Each + scheme's own weekly scale; the band containing normal gross weekly income + in remunerative work, bands including their lower edge ("not less than"); + the lowest amount otherwise (Sch 1 para 8(1)(b) and equivalents); nothing + if exempt.""" + local_authorities = parameters(period).gov.local_authorities + england = ( + local_authorities.england.council_tax_reduction.pensioners.non_dep_deduction + ) + scotland = local_authorities.scotland.council_tax_reduction.non_dep_deduction + wales = local_authorities.wales.council_tax_reduction.non_dep_deduction + country = person.household("country", period) + schemes = [ + is_england_pensioner_scheme(country, pensioner_applicant), + is_scotland_scheme(country), + is_wales_scheme(country), + ] + weekly_income = non_dependant_weekly_gross_income( + person, period, parameters, "council_tax_reduction_non_dep_gross_income" + ) + no_income = np.zeros_like(weekly_income) + remunerative_work_hours = select( + schemes, + [ + england.remunerative_work_hours, + scotland.remunerative_work_hours, + wales.remunerative_work_hours, + ], + default=np.inf, + ) + in_remunerative_work = ( + person("weekly_hours", period) >= remunerative_work_hours + ) & ~is_qualifying_income_related_benefit_claimant( + person, period, parameters, pensioner_applicant + ) + banded = select( + schemes, + [ + england.amount.calc(weekly_income), + scotland.amount.calc(weekly_income), + wales.amount.calc(weekly_income), + ], + default=0, + ) + not_in_remunerative_work = select( + schemes, + [ + england.amount.calc(no_income), + scotland.amount.calc(no_income), + wales.amount.calc(no_income), + ], + default=0, + ) + weekly = where(in_remunerative_work, banded, not_in_remunerative_work) + exempt = non_dep_deduction_exempt(person, period, parameters, pensioner_applicant) + return where(exempt, 0, weekly * WEEKS_IN_YEAR) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicant_has_non_dep_exemption.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicant_has_non_dep_exemption.py index 53c4079bea..f2483e0bbf 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicant_has_non_dep_exemption.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicant_has_non_dep_exemption.py @@ -6,15 +6,15 @@ class council_tax_reduction_applicant_has_non_dep_exemption(Variable): entity = BenUnit label = "No non-dependant deductions from this family's Council Tax Reduction" documentation = ( - "Whether no deduction is made for any non-dependant from this family's " + "Whether the council makes no deduction for any non-dependant from this family's " "Council Tax Reduction because the applicant or partner is blind, or " "receives in respect of themselves Attendance Allowance, the care " "component of Disability Living Allowance, the daily living component " "of Personal Independence Payment or Armed Forces Independence Payment. " "The exemption belongs to each applicant: where families share the " "rent and each claims, one family's disability does not exempt " - "another's claim. The model applies it in the English working-age " - "local schemes." + "another's claim. The model applies it in the national schemes and the " + "English working-age local schemes." ) definition_period = YEAR reference = ( diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction.py index 133d9ab75f..0956c62437 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction.py @@ -1,59 +1,44 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction._national_non_dependants import ( + household_head_family_is_pensioner, + individual_non_dep_deduction, +) class council_tax_reduction_individual_non_dep_deduction(Variable): value_type = float entity = Person label = "CTR individual non-dependent deduction" + documentation = ( + "The England pensioner, Scottish and Welsh schemes' own weekly scales. " + "For a non-dependant in remunerative work, the council deducts the amount for the band " + "containing their normal gross weekly income (a couple's joint income " + "for a claimant or partner of the non-dependant's family); bands " + "include their lower edge. For a non-dependant outside remunerative work, " + "including one on Income Support, income-based JSA or income-related " + "ESA, the council deducts the lowest amount; for an exempt non-dependant it deducts nothing. English working-age local schemes have their own " + "variables. The person-level variable reports the scheme of the household head's family; " + "each applicant's deductions follow their own scheme." + ) definition_period = YEAR unit = GBP defined_for = "council_tax_reduction_individual_non_dep_deduction_eligible" reference = ( "https://www.legislation.gov.uk/uksi/2012/2885/schedule/1/paragraph/8", "https://www.legislation.gov.uk/uksi/2012/2885/regulation/10", - "https://www.legislation.gov.uk/ssi/2021/249/regulation/90", - "https://www.legislation.gov.uk/ssi/2012/319/regulation/48", "https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/3", "https://www.legislation.gov.uk/wsi/2013/3029/schedule/6/paragraph/5", + "https://www.legislation.gov.uk/wsi/2013/3029/regulation/10", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/48", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/6", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/90", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/10", ) def formula(person, period, parameters): - p = parameters(period).gov.dwp.housing_benefit.non_dep_deduction - local = parameters(period).gov.local_authorities - country = person.household("country", period) - countries = country.possible_values - remunerative_work_hours = select( - [country == countries.SCOTLAND, country == countries.WALES], - [ - local.scotland.council_tax_reduction.non_dep_deduction.remunerative_work_hours, - local.wales.council_tax_reduction.non_dep_deduction.remunerative_work_hours, - ], - default=local.england.council_tax_reduction.pensioners.non_dep_deduction.remunerative_work_hours, - ) - income = person("council_tax_reduction_non_dep_gross_income", period) - weekly_total_income = income / WEEKS_IN_YEAR - on_income_related_benefit = ( - person("is_on_income_support", period) - | person("is_on_income_based_jsa", period) - | person("is_on_income_related_esa", period) - ) - in_remunerative_work = ( - person("weekly_hours", period) >= remunerative_work_hours - ) & ~on_income_related_benefit - banded = p.amount.calc(weekly_total_income, right=True) - # The national provisions' paragraph (1)(b) gives a non-dependant - # outside remunerative work the flat amount instead of an income - # band; the existing HB scale is retained pending #2007. The gate - # only lowers a deduction: a non-worker with no banding income keeps - # the scale's zero, because the exemptions in paragraphs (6) to (8) - # (a claimant on Attendance Allowance, a non-dependant on Income - # Support, income-based JSA, income-related ESA, Pension Credit or - # Universal Credit without earnings, a full-time student) are not - # modelled, and the income-related benefits are not banding income, - # so many such non-dependants have none. - deduction = where( - in_remunerative_work, - banded, - min_(banded, p.amount.amounts[0]), + return individual_non_dep_deduction( + person, + period, + parameters, + household_head_family_is_pensioner(person, period), ) - return deduction * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction_eligible.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction_eligible.py index 861bd2dc56..b05ef2f086 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction_eligible.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_individual_non_dep_deduction_eligible.py @@ -1,4 +1,7 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction._legacy import ( + in_other_non_liable_family, +) class council_tax_reduction_individual_non_dep_deduction_eligible(Variable): @@ -6,24 +9,32 @@ class council_tax_reduction_individual_non_dep_deduction_eligible(Variable): entity = Person label = "eligible person for CTR non-dependent deduction" documentation = ( - "An adult outside every family claiming Council Tax Reduction for the " - "household. Someone jointly and severally liable for the council tax " - "with the claimant (a joint tenant or other sharer of the rent) and " - "someone liable to the claimant on a commercial basis (a boarder or " - "lodger) is not a non-dependant." + "A non-dependant aged 18 or over: an adult outside every family " + "claiming Council Tax Reduction for the household who is not liable " + "for rent, or a member of any family's benefit unit who is not its " + "applicant, partner or a child or young person (see " + "is_benefit_unit_non_dependant_for_council_tax_reduction), including " + "a boarder's or lodger's adult son. Someone jointly and severally " + "liable for the council tax with the applicant (a joint tenant or " + "other sharer of the rent) and a boarder or lodger who pays the " + "applicant are not non-dependants." ) definition_period = YEAR reference = ( "https://www.legislation.gov.uk/uksi/2012/2885/regulation/9", - "https://www.legislation.gov.uk/wsi/2013/3029", - "https://www.legislation.gov.uk/ssi/2021/249", + "https://www.legislation.gov.uk/wsi/2013/3029/regulation/9", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/8", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/3", ) def formula(person, period, parameters): - # SI 2012/2885 reg 9(2)(d)-(e) and the Welsh and Scottish equivalents. - return ( - (person("age", period) >= 18) - & ~person.benunit("council_tax_reduction_claimant_benunit", period) - & ~person.benunit("liable_for_share_of_household_rent", period) - & ~person("pays_rent_to_householder", period) + # SI 2012/2885 reg 9(2)(a), (c)-(e) and the Welsh and Scottish + # equivalents; deductions are for non-dependants aged 18 or over. + other_family = in_other_non_liable_family(person, period) + # A member of any family's benefit unit who is not in its applicant's + # family. Reg 9(2)(e) excludes only the person liable to pay the + # applicant, so a boarder's or lodger's adult son is a non-dependant. + in_unit = person( + "is_benefit_unit_non_dependant_for_council_tax_reduction", period ) + return (person("age", period) >= 18) & (other_family | in_unit) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deduction_exempt.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deduction_exempt.py new file mode 100644 index 0000000000..5c965738bb --- /dev/null +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deduction_exempt.py @@ -0,0 +1,47 @@ +from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction._national_non_dependants import ( + household_head_family_is_pensioner, + non_dep_deduction_exempt, +) + + +class council_tax_reduction_non_dep_deduction_exempt(Variable): + value_type = bool + entity = Person + label = "Exempt from national Council Tax Reduction non-dependant deductions" + documentation = ( + "Modelled exemptions in the England pensioner, Scottish and Welsh " + "schemes: a full-time student; a non-dependant on Income Support, " + "income-based JSA, income-related ESA or State Pension Credit (any " + "age), and in Scotland's working-age scheme from April 2022 one whose " + "partner is on Income Support, income-based JSA or income-related ESA " + "and who is not on Universal Credit; an adult for whom someone else is " + "entitled to child benefit (LGFA 1992 Sch 1 para 3); and, from the year " + "each scheme added it, one entitled to Universal Credit calculated on " + "no earned income. Welsh councils deduct for members of the ESA " + "work-related activity group; with no ESA group input, the model " + "exempts every income-related ESA recipient. Not modelled: other " + "persons disregarded for council tax discounts (LGFA 1992 Sch 1), " + "youth training allowances, a normal home elsewhere, and " + "absence in hospital or on armed forces operations." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2012/2885/schedule/1/paragraph/8", + "https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/3", + "https://www.legislation.gov.uk/wsi/2013/3029/schedule/6/paragraph/5", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/48", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/90", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/4", + "https://www.legislation.gov.uk/ukpga/1992/14/schedule/1/paragraph/3", + ) + + def formula(person, period, parameters): + # Under the scheme of the household head's family; each applicant's + # deductions use its own scheme (council_tax_reduction_non_dep_deductions). + return non_dep_deduction_exempt( + person, + period, + parameters, + household_head_family_is_pensioner(person, period), + ) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions.py index 6241abf4c7..ccfa21cb6c 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions.py @@ -1,20 +1,109 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.dwp.housing_benefit.non_dep_deduction._non_dependants import ( + deduction_per_family, +) +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction._national_non_dependants import ( + household_head_family_is_pensioner, + individual_non_dep_deduction, +) +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction.config import ( + is_wales_scheme, +) class council_tax_reduction_non_dep_deductions(Variable): value_type = float entity = BenUnit label = "CTR non-dependent deductions" + documentation = ( + "Deductions for a claiming family's non-dependants, whether in its " + "own benefit unit or another family's: one per couple, the higher of " + "the two members' amounts (both, in the Welsh scheme for people who " + "are not pensioners, when the couple's own Universal Credit award " + "assesses either member), one for each other non-dependant, each " + "under the applicant family's own scheme and apportioned between " + "jointly liable applicants; none if the applicant or partner is exempt." + ) definition_period = YEAR unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/2012/2885/regulation/9", + "https://www.legislation.gov.uk/uksi/2012/2885/schedule/1/paragraph/8", + "https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/3", + "https://www.legislation.gov.uk/wsi/2013/3029/schedule/6/paragraph/5", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/48", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/90", + ) def formula(benunit, period, parameters): - deductions = benunit.members( - "council_tax_reduction_individual_non_dep_deduction", period + person = benunit.members + country = benunit.household("country", period) + # WSI 2013/3029 Sch 6 para 5(3): from a working-age applicant's claim + # the council deducts for each member of a non-dependant couple with a + # Universal Credit award. The award must be the couple's own, one that + # assesses the claimant or partner. Another member's award as a single + # claimant leaves the couple one deduction. + couple_claims_universal_credit = benunit.any( + person("is_uc_assessed_claimant", period) + & person("is_claimant_or_partner", period) + ) + universal_credit_couple = ( + is_wales_scheme(country) + & couple_claims_universal_credit + & (benunit("universal_credit_pre_benefit_cap", period) > 0) + ) + # council_tax_reduction_individual_non_dep_deduction is each person's + # deduction under the scheme of the household head's family. A jointly + # liable applicant whose own family is under the other scheme (SI + # 2012/2885 reg 3) has its deductions recomputed under that scheme. + head_pensioner = household_head_family_is_pensioner(person, period) + deductions = { + "head": person( + "council_tax_reduction_individual_non_dep_deduction", period + ), + "other": person( + "council_tax_reduction_individual_non_dep_deduction_eligible", period + ) + * individual_non_dep_deduction(person, period, parameters, ~head_pensioner), + } + # Every eligible non-dependant normally resides with each liable + # person (SI 2012/2885 reg 9(1)): another family's adult, an + # applicant's own adult son, or a boarder's or lodger's (reg 9(2)(e) + # excludes only the person liable to pay the applicant). Members of + # the applicant's own family are not its non-dependants (reg + # 9(2)(a)); the non-dependants in its benefit unit are. + in_unit = person( + "is_benefit_unit_non_dependant_for_council_tax_reduction", period + ) + head_family_pensioner = benunit.any(head_pensioner) + non_dependants = {} + for scheme, amounts in deductions.items(): + pensioner = head_family_pensioner ^ (scheme == "other") + both = universal_credit_couple & ~pensioner + family_amount = deduction_per_family(benunit, period, amounts, both) + # Each family's amount counted once. + counted = person("is_benunit_head", period) * benunit.project(family_amount) + in_household = benunit.max(person.household.sum(counted)) + own_in_unit = deduction_per_family(benunit, period, amounts * in_unit, both) + non_dependants[scheme] = in_household - family_amount + own_in_unit + own_scheme_is_heads = ( + benunit("council_tax_reduction_pensioner", period) == head_family_pensioner ) - deductions_in_household = benunit.max(benunit.members.household.sum(deductions)) - deductions_in_benunit = benunit.sum(deductions) # A non-dependant of two or more jointly liable people is apportioned - # equally between them (SI 2012/2885 Sch 1 para 8(5)). + # equally between them (Sch 1 para 8(5)). share = benunit("council_tax_reduction_joint_liability_share", period) - return (deductions_in_household - deductions_in_benunit) * share + # Only a family that claims bears deductions; a non-dependant's own + # family does not. + claims = benunit("council_tax_reduction_claimant_benunit", period) + applicant_exempt = benunit( + "council_tax_reduction_applicant_has_non_dep_exemption", period + ) + return where( + applicant_exempt, + 0, + claims + * where( + own_scheme_is_heads, non_dependants["head"], non_dependants["other"] + ) + * share, + ) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.py index 24b75757db..01e88a693e 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_gross_income.py @@ -20,27 +20,29 @@ class council_tax_reduction_non_dep_gross_income(Variable): "https://www.legislation.gov.uk/wsi/2013/3029/schedule/6/paragraph/5", ) documentation = ( - "Annual gross income represented by the model for non-dependant " + "Annual gross income the model represents for the non-dependant " "bands, before tax, National Insurance, employment deductions and " - "pension contributions. Signed trading/property inputs are retained; " - "a separate cross-source loss-offset rule is not modelled. Claimant " - "war-pension and supplementary-pension disregards do not apply to " - "these non-dependant bands. It includes statutory sick, maternity and " - "paternity pay once through employment_benefits, child and spousal " - "maintenance, the tax-purpose social security income sources, and " - "the separately represented non-taxable periodic benefits. " - "Attendance Allowance, DLA and PIP are omitted under the specific " - "disability-payment disregards. The other disregarded disability " - "and compensation/trust payments have no separate model inputs. " - "Benefit-unit awards (including Child Benefit, tax credits and UC) " - "are not allocated to an individual recipient here; adding computed " - "UC would also create a cycle through the benefit cap and HB. " - "BSP and AFCS inputs do not separate periodic income from capital " - "payments, so they are not added. Other investment income remains " - "excluded to preserve this income base; extending it remains for " - "#2007. The deduction formula applies the remunerative-work gate. The " - "gross-income variable itself does not decide whether someone is " - "in remunerative work or whether a deduction is due." + "pension contributions. DWP tells councils to count earnings and any " + "other income (HB guidance A5.542), to count a self-employed " + "non-dependant's total income before business expenses (A5.551) and " + "to add savings interest to earnings (A5.570). The model holds " + "self-employment and property profits rather than receipts, so it " + "adds each profit and counts a loss as nil, and a loss never reduces " + "other income. The claimant war-pension and supplementary-pension " + "disregards do not apply to these bands. The total includes " + "statutory sick, maternity and paternity pay once, through " + "employment_benefits; child and spousal maintenance; the tax-purpose " + "social security income sources; the non-taxable periodic benefits " + "the model represents separately; and other investment income, ISA " + "interest (tax_free_savings_income) and ISA dividends. It leaves out " + "Attendance Allowance, DLA and PIP under the disability-payment " + "disregards; the other disregarded disability, compensation and " + "trust payments have no model inputs. BSP and AFCS inputs do not " + "separate periodic income from capital payments, so the total leaves " + "them out. Benefit-unit awards, such as Child Benefit, tax credits " + "and Universal Credit, stay out of it. The deduction formula adds " + "family awards to the claimant and partner's joint income and applies " + "the remunerative-work test." ) def formula(person, period, parameters): @@ -52,10 +54,13 @@ def formula(person, period, parameters): "employment_benefits", "private_pension_income", "social_security_income", - "self_employment_income", - "property_income", "savings_interest_income", + # Count ISA interest unless the inputs supply an exempt-interest + # total. + "tax_free_savings_income", + "other_investment_income", "dividend_income", + "individual_savings_account_dividend_income", "miscellaneous_income_before_trading_allowance", "maternity_allowance", "iidb", @@ -63,8 +68,15 @@ def formula(person, period, parameters): "war_widows_pension", ], ) + # The model has profits, not the receipts DWP HB guidance A5.551 + # counts before business expenses. As an approximation, a loss + # counts as nil, so it never reduces other income. + losses_as_nil = max_(0, person("self_employment_income", period)) + max_( + 0, person("property_income", period) + ) return ( income + + losses_as_nil + max_(0, person("maintenance_income", period)) + max_(0, person("spousal_maintenance_income", period)) ) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/is_benefit_unit_non_dependant_for_council_tax_reduction.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/is_benefit_unit_non_dependant_for_council_tax_reduction.py new file mode 100644 index 0000000000..97d82a2e47 --- /dev/null +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/is_benefit_unit_non_dependant_for_council_tax_reduction.py @@ -0,0 +1,42 @@ +from policyengine_uk.model_api import * + + +class is_benefit_unit_non_dependant_for_council_tax_reduction(Variable): + value_type = bool + entity = Person + label = "Non-dependant within the benefit unit for Council Tax Reduction" + documentation = ( + "A member of the benefit unit who is neither the Council Tax " + "Reduction applicant or partner (is_council_tax_reduction_applicant_or_partner) " + "nor a child or young person: someone aged 16 or over who is not a " + "Child Benefit qualifying young person. Such a person is outside the " + "applicant's family but normally resides with the applicant, so is a " + "non-dependant. Usually the applicant is the benefit unit's claimant, " + "and this is the same set as " + "is_benefit_unit_non_dependant_for_legacy_benefits. Where a household " + "head who is not the benefit unit's claimant or partner applies alone " + "(council_tax_reduction_head_applies_alone), the benefit unit's " + "claimant and partner are the head's non-dependants. This person's own " + "benefit awards are not modelled, so the exemptions for a " + "non-dependant on an income-related benefit or on Universal Credit " + "with no earned income never apply to them." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2012/2885/regulation/9", + "https://www.legislation.gov.uk/wsi/2013/3029/regulation/9", + "https://www.legislation.gov.uk/ssi/2021/249/regulation/8", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/3", + ) + + def formula(person, period, parameters): + # SI 2012/2885 and WSI 2013/3029 reg 9(1), (2)(a), (c); SSI 2021/249 + # reg 8(2)(a)-(b); SSI 2012/319 reg 3: anyone normally residing with the + # applicant except the applicant's own family. + applicant_or_partner = person( + "is_council_tax_reduction_applicant_or_partner", period + ) + child_or_young_person = person( + "is_child_or_qualifying_young_person_for_child_benefit", period + ) + return ~applicant_or_partner & ~child_or_young_person