diff --git a/changelog.d/2014.fixed.md b/changelog.d/2014.fixed.md new file mode 100644 index 0000000000..b3c0b44145 --- /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), and apply the Universal Credit renter exemption from housing cost contributions (Sch 4 para 15). 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..ffa7cbe70f --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/benefit_unit_non_dependants.yaml @@ -0,0 +1,798 @@ +# 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)). +- 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: 70 + is_household_head: true + is_claimant_or_partner: true + daughter: + age: 45 + 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] + +# 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] 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..3d8f2812d9 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,7 +1,7 @@ - name: Eligible period: 2025 input: - benunit_is_rent_liable: false + is_non_dependant_of_household_head: true age: 18 output: housing_benefit_individual_non_dep_deduction_eligible: true @@ -9,7 +9,7 @@ - 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 +21,23 @@ 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. +- 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: 19 + 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 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 1f7f3eada3..75d21ea9ce 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/test_benefit_unit_non_dependant_properties.py b/policyengine_uk/tests/test_benefit_unit_non_dependant_properties.py new file mode 100644 index 0000000000..45a5ab4714 --- /dev/null +++ b/policyengine_uk/tests/test_benefit_unit_non_dependant_properties.py @@ -0,0 +1,428 @@ +"""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, 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 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; each Council Tax Reduction + claiming family bears every non-dependant in proportion to its + joint-liability share; 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 under Sch 4 para 15 has no + Universal Credit 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. +5. Bedrooms: every Universal Credit non-dependant within a benefit unit has a + bedroom in the size criteria (para 10(1)(c)), so the deduction and the + bedroom use one definition. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +# 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()), + 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" + people[pid] = { + "age": 8, + "is_claimant_or_partner": False, + "is_household_head": False, + } + ids.append(pid) + person_rows.append( + dict( + household=h, + family=len(family_rows), + kind="child", + age=8, + disability_benefit=False, + ) + ) + benunit = {"members": ids} + 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 oracle(person_rows, family_rows, uc, hb, ctr, pension_credit, claims, ctr_share): + """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. + """ + 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)) + 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] + head = fams[0] + # 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 is the head's + # non-dependant; a non-QYP member aged 16+ of any other family is + # their own family's. + # 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 + bearer = head if other_family else f + if uc_non_dependant 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 + exp_ctr[i] += claims[i] * ctr_share[i] * ctr[p] + return exp_uc, exp_hb, exp_ctr, expected_uc_individual + + +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"), + ) + 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. + 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. + for variable in ( + "housing_benefit_individual_non_dep_deduction_eligible", + "council_tax_reduction_individual_non_dep_deduction_eligible", + ): + assert np.array_equal( + calc(sim, variable) > 0, + eligible(("head", "sharer", "lodger", "non_dependant")), + ) + + +@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 is borne in full across the joint + # occupiers, and a lodger's own non-dependant once more by the lodger. + 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(v["hb"] * (1 + lodgers_own), "person", "household") + assert np.allclose(hb_families, hb_people, atol=0.01) + # CTR: every non-dependant is borne by the claiming families in + # proportion to their joint-liability shares (which follow the + # regulations' per-person wording, so they need not sum to one). + claim_shares = sim.map_result(v["claims"] * v["ctr_share"], "benunit", "household") + ctr_families = sim.map_result( + calc(sim, "council_tax_reduction_non_dep_deductions"), "benunit", "household" + ) + ctr_people = sim.map_result(v["ctr"], "person", "household") + assert np.allclose(ctr_families, claim_shares * ctr_people, atol=0.01) + # 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) + 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, charged to + # the head's family (UC), by rent share (HB) or by claim share (CTR). + 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] + + head_family = np.array([row["role"] == "head" for row in family_rows]) + 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, head_family * other_families(v["uc"])), + atol=0.01, + ) + assert np.allclose( + calc(sim, "housing_benefit_non_dep_deductions"), + calc(sim, "share_of_household_rent") * other_families(v["hb"]), + atol=0.01, + ) + assert np.allclose( + calc(sim, "council_tax_reduction_non_dep_deductions"), + v["claims"] * v["ctr_share"] * other_families(v["ctr"]), + atol=0.01, + ) + + +@PROPERTY_SETTINGS +@given(population) +def test_every_non_dependant_in_a_benefit_unit_has_a_bedroom(population): + sim, person_rows, family_rows, v = run(population) + # 5. Para 10(1)(b)-(c): a bedroom for each responsible qualifying young + # person and each non-dependant within the unit; the rest of the count + # (renter, children, other families' non-dependants) is unchanged when + # those members are made dependants instead. + in_unit = calc(sim, "is_benefit_unit_non_dependant_for_universal_credit") + kinds = np.array([row["kind"] for row in person_rows]) + assert np.array_equal(in_unit > 0, kinds == "non_dependant") + rooms = calc(sim, "LHA_allowed_bedrooms") + as_dependants, *_ = run(population, {"non_dependant": "dependant"}) + rooms_as_dependants = calc(as_dependants, "LHA_allowed_bedrooms") + # A non-dependant has a bedroom as a responsible qualifying young person + # would, so the count is the same either way. + assert np.array_equal(rooms, rooms_as_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 c3b3efca5a..823c384da4 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 @@ -12,9 +12,11 @@ 3. Tenure: a boarder's or lodger's family rents privately, is never in social housing, and is LHA-eligible. 4. Non-dependants: a family liable for rent is never a non-dependant; only - the household head's family has Universal Credit non-dependant - deductions; a boarder's or lodger's family has no Housing Benefit or - Council Tax Reduction non-dependant deductions. + the household head's family has Universal Credit deductions for + non-dependants in other families; a boarder's or lodger's family has no + Housing Benefit or Council Tax Reduction deductions for them. (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 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..164a42b907 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,24 @@ 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)." + ) 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 + return non_dependant & age_eligible 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..66fa7799c8 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 @@ -5,26 +5,53 @@ class housing_benefit_non_dep_deductions(Variable): value_type = float entity = BenUnit label = "non-dependent deductions" + documentation = ( + "Deductions for the claimant's non-dependants: 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. Each non-dependant 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." + ) 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 # (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)). 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) + other_family = person("is_non_dependant_of_household_head", period) + # 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). + # 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)). share = benunit("share_of_household_rent", period) - return share * benunit.max(person.household.sum(deductions)) + from_joint_occupiers = share * benunit.max( + person.household.sum(deductions * (other_family | own_family)) + ) + # 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)) + return from_joint_occupiers + from_own_family 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..7819dc646c --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_benefit_unit_non_dependant_for_legacy_benefits.py @@ -0,0 +1,46 @@ +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." + ) + 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 + ) + return ~claimant_or_partner & ~child_or_young_person 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..bc6b71d000 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_benefit_unit_non_dependant_for_universal_credit.py @@ -0,0 +1,36 @@ +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." + ) + 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 + ) + return not_a_child & ~claimant_or_partner & ~qualifying_young_person 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 432cfb0936..512d2e32b3 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 @@ -11,13 +11,18 @@ class uc_non_dep_deduction_exempt(Variable): "receiving Pension Credit, the middle or higher rate of the DLA care " "component, the PIP daily living component or Attendance Allowance, or " "who is entitled to Carer's Allowance (underlying entitlement included, " - "so the carer test is is_carer_for_benefits rather than receipt)." + "so the carer test is is_carer_for_benefits rather than receipt). " + "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" def formula(person, period, parameters): + receives_pension_credit = person("is_claimant_or_partner", period) & ( + person.benunit("pension_credit", period) > 0 + ) return ( - (person.benunit("pension_credit", period) > 0) + receives_pension_credit | 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 eca4abcd2a..20509cd891 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,23 +5,42 @@ 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, for the household head's family " + "only, and those within the renter's own benefit unit. 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 live outside the household head's family and are - # not liable for rent, and only the household head's family has them - # (para 9(2)(d)-(f)): a sharer, boarder or lodger has none from the - # household head's family. 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 live outside the household + # head's family and are not liable for rent. A non-dependant counts + # in one claim only (para 9(2)(f)), which the model gives to the + # household head's family, so a sharer, boarder or lodger has none + # from other families. + other_family = person("is_non_dependant_of_household_head", period) head_family = benunit.any(person("is_household_head", period)) - return head_family * benunit.max(person.household.sum(deductions)) + from_other_families = head_family * benunit.max( + person.household.sum(deductions * other_family) + ) + # A member of the renter's own benefit unit who is not the claimant, + # partner or a qualifying young person is a non-dependant of the + # renter (para 9(1)(c) and (2)(a), (g)). + own_family = ( + person("is_benefit_unit_non_dependant_for_universal_credit", period) + & ~other_family + ) + from_own_family = benunit.sum(deductions * own_family) + renter_exempt = benunit("uc_non_dep_deductions_renter_exempt", period) + return where(renter_exempt, 0, from_other_families + 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 966d230ff7..fd35957f53 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,20 @@ ) +def in_other_non_liable_family(person, period): + """Whether each person is in a family that is neither the oldest adult's + nor liable for the rent. This stands in for SI 2012/2885 reg 9(2)(a) and + (d)-(e): such a family is outside the claimant's family, and not jointly + liable or paying the claimant. It is a proxy: a family jointly liable for + the council tax but paying no rent is wrongly included, which #2009's + claimant-family test fixes. The eligibility for a non-dependant deduction + and the local schemes' aggregation both use this, so they stay on one + definition.""" + return ~person.benunit("benunit_contains_household_head", period) & ~person.benunit( + "benunit_is_rent_liable", period + ) + + def is_full_time_student_non_dep(person, period): return (person("current_education", period) != EducationType.NOT_IN_EDUCATION) | ( person("in_HE", period) @@ -74,7 +88,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/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 459598f8d3..2c39ee3daa 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,23 +9,29 @@ class council_tax_reduction_individual_non_dep_deduction_eligible(Variable): entity = Person label = "eligible person for CTR non-dependent deduction" documentation = ( - "An adult outside the claimant's family who is not liable for rent. " - "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 the claimant's " + "family who is not liable for rent, or a member of any family's " + "benefit unit who is not its claimant, partner or a child or young " + "person (see is_benefit_unit_non_dependant_for_legacy_benefits), " + "including a boarder's or lodger's adult son. Someone jointly and " + "severally liable for the council tax with the claimant (a joint " + "tenant or other sharer of the rent) and a boarder or lodger who pays " + "the claimant 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("benunit_contains_household_head", period) - & ~person.benunit("benunit_is_rent_liable", 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 claimant'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_legacy_benefits", 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_deductions.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_non_dep_deductions.py index 6241abf4c7..b2589bfae4 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 @@ -5,16 +5,36 @@ 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, apportioned equally between " + "the jointly liable people." + ) 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", + ) def formula(benunit, period, parameters): - deductions = benunit.members( + person = benunit.members + deductions = person( "council_tax_reduction_individual_non_dep_deduction", period ) - 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)). + claims = benunit("council_tax_reduction_claimant_benunit", period) + # 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). A + # non-dependant of two or more jointly liable people is apportioned + # equally between them (Sch 1 para 8(5)). Members of the applicant's + # own family are not its non-dependants (reg 9(2)(a)). + own_family_member = ~person( + "is_benefit_unit_non_dependant_for_legacy_benefits", period + ) + non_dependants = benunit.max(person.household.sum(deductions)) - benunit.sum( + deductions * own_family_member + ) share = benunit("council_tax_reduction_joint_liability_share", period) - return (deductions_in_household - deductions_in_benunit) * share + return claims * share * non_dependants