diff --git a/changelog.d/2014-local.fixed.md b/changelog.d/2014-local.fixed.md new file mode 100644 index 0000000000..29cf62eda1 --- /dev/null +++ b/changelog.d/2014-local.fixed.md @@ -0,0 +1 @@ +Charge non-dependant deductions in the English working-age local Council Tax Reduction schemes (Merton, Kingston upon Thames, Newham, Westminster, Oxford) as the national schemes do: for adults in the applicant's own benefit unit (the applicant identified as for the means test, so a grandmother who applies alone is charged for the couple in her benefit unit), separately for each adult in another family who is not its claimant or partner, and apportioned between jointly liable applicants. Band a claimant or partner on the couple's joint income and anyone else on their own, each on their own hours; exempt a non-dependant on Income Support, income-based JSA, income-related ESA or State Pension Credit only if the award is payable to them, and one on Universal Credit only if they have no earned income of their own; and deduct for each member of a Universal Credit couple in Westminster, as the Default Scheme does. In every scheme, national and local, someone who pays the householder for their own board or lodging, or is a joint tenant, is not a non-dependant even where the rest of their family is, except a member of the household head's own family who pays board, who is taken to be a close relative. 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 index cbdae837e3..124dd76188 100644 --- 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 @@ -987,10 +987,11 @@ 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 +# The English working-age local schemes reproduce the Default Scheme's para 9 +# and para 30(5): the head's own son normally resides with both jointly liable +# people, so each bears half his deduction (Newham para 30B: £18.84 a week on +# £384.62, £979.68 a year). 70% of each half of the £1,800 is £630. +- name: Local CTR (Newham), jointly liable families share the deduction for the head's own in-unit son period: 2025 absolute_error_margin: 0.01 input: @@ -1025,8 +1026,8 @@ 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] + newham_council_tax_reduction_non_dep_deductions: [489.84, 489.84] + newham_council_tax_reduction: [140.16, 140.16] # 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 @@ -1364,3 +1365,124 @@ output: uc_non_dependants_counted: [false, true] uc_non_dep_deductions: [0, 1_158.60] + +# SI 2012/2885 reg 9(2)(e) excludes someone liable to pay the applicant on a +# commercial basis, but reg 9(3)(a)(i) keeps as a non-dependant one who is a +# close relative of the applicant or partner (#2559 checks the Welsh and +# Scottish equivalents' wording). The model has no relationship +# input: a member of the household head's own family who pays board is taken +# to be a close relative, so stays a non-dependant. The son is not in +# remunerative work, so pays each scheme's lowest amount for 2026-27. +# England's pensioner scheme: 5.20 a week (SI 2012/2885 Sch 1 para 8(1)(b)). +- name: CTR (England pensioners), a son who pays his mother board stays her non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + mother: + age: 70 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + rent_paid_as_boarder: 2_600 + benunits: + benunit: + members: [mother, son] + households: + household: + members: [mother, son] + country: ENGLAND + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, true] + council_tax_reduction_non_dep_deductions: 270.40 + +# Scotland's working-age scheme: 5.55 a week (SSI 2021/249 reg 90). +- name: CTR (Scotland), a son who pays his mother board stays her non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + mother: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + rent_paid_as_boarder: 2_600 + benunits: + benunit: + members: [mother, son] + households: + household: + members: [mother, son] + country: SCOTLAND + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, true] + council_tax_reduction_non_dep_deductions: 288.60 + +# Wales's working-age scheme: 6.70 a week (WSI 2013/3029 Sch 6 para 5). +- name: CTR (Wales), a son who pays his mother board stays her non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + mother: + age: 50 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + rent_paid_as_boarder: 2_600 + benunits: + benunit: + members: [mother, son] + households: + household: + members: [mother, son] + country: WALES + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, true] + council_tax_reduction_non_dep_deductions: 348.40 + +# Someone outside the household head's family who pays the householder is +# excluded personally (reg 9(2)(e)), even where the rest of their family pays +# nothing; the lodger's son who does not pay is still a non-dependant (see +# "CTR, a lodger's adult son is the applicant's non-dependant"). +- name: CTR (Scotland), a lodger's adult son who pays the householder himself is not a non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 50 + 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 + current_education: NOT_IN_EDUCATION + rent_paid_as_lodger: 2_600 + benunits: + benunit_1: + members: [head] + benunit_2: + members: [lodger, lodger_son] + households: + household: + members: [head, lodger, lodger_son] + rent: 9_000 + country: SCOTLAND + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, false, false] + council_tax_reduction_non_dep_deductions: [0, 0] diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/applicant_or_partner.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/applicant_or_partner.yaml index 42ee19cbed..083128c4d2 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/applicant_or_partner.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/applicant_or_partner.yaml @@ -140,10 +140,8 @@ simulated_council_tax_reduction_benunit: [759.60] - name: The young couple's income-based Jobseeker's Allowance does not passport the grandmother head - # Provisional until in-unit non-dependants land (#2017): the 19-year-old - # parents are her non-dependants (SI 2012/2885 reg 9(1); they are not in - # her family), but the model charges a deduction only for non-dependants - # in another benefit unit, so this case has none. + # The 19-year-old parents are her non-dependants (SI 2012/2885 reg 9(1); + # they are not in her family), and neither is in education. period: 2026 absolute_error_margin: 0.01 input: @@ -156,10 +154,12 @@ age: 19 is_parent: true is_household_head: false + current_education: NOT_IN_EDUCATION young_father: age: 19 is_parent: true is_household_head: false + current_education: NOT_IN_EDUCATION baby: age: 0 is_household_head: false @@ -183,7 +183,12 @@ council_tax_reduction_relevant_income_based_benefit: [false] # Neither the couple's JSA nor their Child Benefit is her income. council_tax_reduction_applicable_income: [18_514] - simulated_council_tax_reduction_benunit: [759.60] + # 759.60 before non-dependant deductions. The JSA is payable to one of + # the couple, who is exempt (Sch 1 para 8(8)); the other, not in + # remunerative work, pays the lowest amount, 5.20 a week, the couple's + # one deduction (para 8(3)): 270.40. + council_tax_reduction_non_dep_deductions: [270.40] + simulated_council_tax_reduction_benunit: [489.20] - name: A head who applies alone is a single person, with premiums on her own disability only period: 2026 @@ -323,10 +328,8 @@ merton_council_tax_reduction: [1_800, 0] - name: The young couple's Universal Credit does not put a grandmother head on Newham's Universal Credit route - # Provisional until in-unit non-dependants land (#2017): the 19-year-old - # parents are her non-dependants (SI 2012/2885 reg 9(1); they are not in - # her family), but the model charges a deduction only for non-dependants - # in another benefit unit, so this case has none. + # The 19-year-old parents are her non-dependants (para 9(1); they are not + # in her family), and neither is in education. period: 2026 absolute_error_margin: 0.01 input: @@ -338,10 +341,13 @@ age: 19 is_parent: true is_household_head: false + current_education: NOT_IN_EDUCATION young_father: age: 19 is_parent: true is_household_head: false + current_education: NOT_IN_EDUCATION + weekly_hours: 37.5 employment_income: 30_000 baby: age: 0 @@ -365,9 +371,13 @@ council_tax_reduction_applicable_income: [0] # Single aged 25 or over: 95.55 * 52. council_tax_reduction_applicable_amount: [4_968.60] - # No excess income: 1,800 * 70%. On the Universal Credit route the - # couple's earnings and award would leave nothing. - newham_council_tax_reduction: [1_260] + # No excess income: 1,800 * 70% = 1,260. On the Universal Credit route + # the couple's earnings and award would leave nothing. The couple pay one + # deduction (para 30B(3)): the father's £22.61 a week on their joint + # £576.92, 1,175.72 a year; the mother, on Universal Credit with no + # earned income of her own, is exempt. + newham_council_tax_reduction_non_dep_deductions: [1_175.72] + newham_council_tax_reduction: [84.28] - name: A partner under 18 is not liable, but their income counts in the couple's means test period: 2026 @@ -501,10 +511,10 @@ council_tax_reduction_applicable_amount: [7_807.80] - name: The young couple's Universal Credit does not remove a grandmother head's tariff income from capital in Kingston upon Thames - # Provisional until in-unit non-dependants land (#2017): the 19-year-old - # parents are her non-dependants (SI 2012/2885 reg 9(1); they are not in - # her family), but the model charges a deduction only for non-dependants - # in another benefit unit, so this case has none. + # The 19-year-old parents are her non-dependants (para 9(1); they are not + # in her family), and neither is in education. Both are on the couple's + # Universal Credit with no earned income, so both are exempt under para + # 30A's Universal Credit exemption, and there is no deduction. period: 2026 absolute_error_margin: 0.01 input: @@ -517,10 +527,12 @@ age: 19 is_parent: true is_household_head: false + current_education: NOT_IN_EDUCATION young_father: age: 19 is_parent: true is_household_head: false + current_education: NOT_IN_EDUCATION baby: age: 0 is_household_head: false @@ -543,6 +555,7 @@ council_tax_reduction_applicable_amount: [4_968.60] # Tariff income ceil((10,000 - 6,000) / 250) = £16 a week, 832 a year, # since she has no award. 1,800 - (5,000 + 832 - 4,968.60) * 20%. + kingston_upon_thames_council_tax_reduction_non_dep_deductions: [0] kingston_upon_thames_council_tax_reduction: [1_627.32] - name: A blind grandmother head's exemption does not exempt a sharer's claim from a non-dependant deduction in Merton diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/local_scheme_non_dependants.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/local_scheme_non_dependants.yaml new file mode 100644 index 0000000000..4b6b546772 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/local_scheme_non_dependants.yaml @@ -0,0 +1,945 @@ +# Non-dependant deductions in the English working-age local schemes. +# +# Each council's scheme reproduces the Default Scheme's definition: a +# non-dependant is anyone who normally resides with the applicant other than +# a member of the applicant's family (a partner, or a child or qualifying young +# person the applicant is responsible for), a joint occupier, a commercial +# lodger or a paid carer (para 9; Oxford paras 42 and Annex 1). So an adult in +# the applicant's own benefit unit who is not the claimant, partner, child or +# young person is a non-dependant, and so is each adult in another family. +# Each non-dependant aged 18 or over is banded on their own normal gross weekly +# income and is in remunerative work on their own hours (para 30(1)-(2) and +# para 10); a couple's income is joint and the couple pays one deduction, the +# higher (para 30(3)-(4); Oxford para 44), except that Merton, Kingston upon +# Thames and Westminster deduct for each member of a couple with a Universal +# Credit award (the Default Scheme's para 30(3), on which Westminster's +# working-age scheme is based). The benefit-receipt exemptions (para 30(8)) apply to the person on +# the award: Income Support, income-based JSA, income-related ESA and State +# Pension Credit to the member they are payable to, and Universal Credit to +# each joint claimant who has no earned income of their own. +# +# Weekly amounts for 2026-27 (52 weeks a year): +# - Merton (para 30) and Westminster (Appendix A), Oxford (para 43): £5.20 not +# in remunerative work or under £279; £10.60 from £279; £13.30 from £485; +# £15.95 from £605. +# - Kingston upon Thames (para 30A): £5.64; £11.50 from £279; £14.43 from +# £485; £17.31 from £605. +# - Newham (para 30B): £7.54; £14.96 from £183; £18.84 from £316; £22.61 from +# £394. +# Earnings of £30,000 are £576.92 a week, £20,000 are £384.62 and £12,000 are +# £230.77. The claimant has no income and no capital, so Merton, Kingston +# upon Thames, Westminster and Oxford support 100% of the £1,800 council tax +# and Newham 70% (£1,260), less the deductions. +# +# The claimant and partner flags are set because the model's inference treats +# an adult child as a lone parent's partner. + +- name: Merton charges the applicant's own non-dependant son for his own earnings + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + benunit: + members: [parent, son] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [parent, son] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, true] + # £13.30 a week on £576.92. + merton_council_tax_reduction_individual_non_dep_deduction: [0, 691.60] + merton_council_tax_reduction_non_dep_deductions: 691.60 + council_tax_reduction: 1_108.40 + +- name: Merton charges a 19-year-old who has left education in the applicant's benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_household_head: true + is_claimant_or_partner: true + daughter: + # Left education, so not a qualifying young person or a student (the + # education input otherwise defaults to tertiary at 19). + age: 19 + is_claimant_or_partner: false + current_education: NOT_IN_EDUCATION + employment_income: 12_000 + weekly_hours: 30 + benunits: + benunit: + members: [parent, daughter] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [parent, daughter] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + is_benefit_unit_non_dependant_for_legacy_benefits: [false, true] + # In remunerative work on £230.77 a week, below £279: £5.20. + merton_council_tax_reduction_non_dep_deductions: 270.40 + council_tax_reduction: 1_529.60 + +- name: Merton deducts separately for a lone parent and her working son in another family + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + mother: + age: 40 + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + other_family: + members: [mother, son] + would_claim_uc: false + households: + household: + members: [claimant, mother, son] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # The mother is not in remunerative work (£5.20) and is not banded on + # her son's earnings; the son pays £13.30 on his own. + merton_council_tax_reduction_individual_non_dep_deduction: [0, 270.40, 691.60] + merton_council_tax_reduction_non_dep_deductions: [962, 0] + council_tax_reduction: 838 + +- name: Merton exempts a non-dependant on Income Support but not the adult son in her benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + mother: + age: 40 + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + other_family: + members: [mother, son] + would_claim_uc: false + income_support: 1 + households: + household: + members: [claimant, mother, son] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # Para 30(8)(a): the Income Support is the mother's, not her son's. + merton_council_tax_reduction_individual_non_dep_deduction: [0, 0, 691.60] + council_tax_reduction: 1_108.40 + +- name: Merton exempts a Universal Credit non-dependant without earnings but not her earning son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + mother: + age: 40 + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + other_family: + members: [mother, son] + universal_credit: 1_000 + households: + household: + members: [claimant, mother, son] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # Para 30(8)(c): the mother's award is calculated on no earned income, as + # her son's earnings are not hers; he is not on her award. + merton_council_tax_reduction_individual_non_dep_deduction: [0, 0, 691.60] + merton_council_tax_reduction_non_dep_deductions: [691.60, 0] + +- name: Merton deducts for each member of a Universal Credit couple on their own hours and earnings + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + worker: + age: 35 + is_claimant_or_partner: true + employment_income: 30_000 + weekly_hours: 37.5 + partner: + age: 35 + is_claimant_or_partner: true + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + couple: + members: [worker, partner] + universal_credit: 1_000 + households: + household: + members: [claimant, worker, partner] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # Para 30(3): no single deduction where there is a UC award. The worker + # pays £13.30 on the couple's joint £576.92. Para 30(8)(c) exempts one + # entitled to UC "calculated on the basis that the person does not have + # any earned income", read on each person's own earnings as in the + # national schemes: the partner has none, so pays nothing; the worker is + # not exempt. + merton_council_tax_reduction_individual_non_dep_deduction: [0, 691.60, 0] + merton_council_tax_reduction_non_dep_deductions: [691.60, 0] + council_tax_reduction: 1_108.40 + +- name: Merton makes one deduction for a couple not on Universal Credit, on joint income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + first: + age: 35 + is_claimant_or_partner: true + employment_income: 15_000 + weekly_hours: 37.5 + second: + age: 35 + is_claimant_or_partner: true + employment_income: 15_000 + weekly_hours: 37.5 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + couple: + members: [first, second] + would_claim_uc: false + households: + household: + members: [claimant, first, second] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # Para 30(4): £288.46 each, £576.92 jointly: £13.30, deducted once. + merton_council_tax_reduction_individual_non_dep_deduction: [0, 691.60, 691.60] + merton_council_tax_reduction_non_dep_deductions: [691.60, 0] + council_tax_reduction: 1_108.40 + +- name: Merton makes no deduction for the son of an applicant who receives PIP daily living + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_household_head: true + is_claimant_or_partner: true + pip_dl: 1_000 + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + benunit: + members: [parent, son] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [parent, son] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # Para 30(6)(b)(iii). + merton_council_tax_reduction_non_dep_deductions: 0 + council_tax_reduction: 1_800 + +- name: Kingston upon Thames charges the applicant's own non-dependant son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + benunit: + members: [parent, son] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [parent, son] + country: ENGLAND + local_authority: KINGSTON_UPON_THAMES + council_tax: 1_800 + savings: 0 + output: + # Para 30A: £14.43 a week on £576.92. + kingston_upon_thames_council_tax_reduction_non_dep_deductions: 750.36 + council_tax_reduction: 1_049.64 + +- name: Kingston upon Thames deducts separately for a lone parent and her working son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + mother: + age: 40 + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 20_000 + weekly_hours: 37.5 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + other_family: + members: [mother, son] + would_claim_uc: false + households: + household: + members: [claimant, mother, son] + country: ENGLAND + local_authority: KINGSTON_UPON_THAMES + council_tax: 1_800 + savings: 0 + output: + # Mother £5.64; son £11.50 on £384.62. + kingston_upon_thames_council_tax_reduction_individual_non_dep_deduction: [0, 293.28, 598] + council_tax_reduction: 908.72 + +- name: Newham charges the applicant's own non-dependant son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + benunit: + members: [parent, son] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [parent, son] + country: ENGLAND + local_authority: NEWHAM + council_tax: 1_800 + savings: 0 + output: + # Para 30B: £22.61 a week on £576.92; 70% of £1,800 is £1,260. + newham_council_tax_reduction_non_dep_deductions: 1_175.72 + council_tax_reduction: 84.28 + +- name: Newham deducts separately for a lone parent and her working son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + mother: + age: 40 + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 12_000 + weekly_hours: 37.5 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + other_family: + members: [mother, son] + would_claim_uc: false + households: + household: + members: [claimant, mother, son] + country: ENGLAND + local_authority: NEWHAM + council_tax: 1_800 + savings: 0 + output: + # Mother £7.54; son £14.96 on £230.77. + newham_council_tax_reduction_individual_non_dep_deduction: [0, 392.08, 777.92] + council_tax_reduction: 90 + +- name: Newham makes one deduction for a Universal Credit couple, the working member's + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + worker: + age: 35 + is_claimant_or_partner: true + employment_income: 30_000 + weekly_hours: 37.5 + partner: + age: 35 + is_claimant_or_partner: true + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + couple: + members: [worker, partner] + universal_credit: 1_000 + households: + household: + members: [claimant, worker, partner] + country: ENGLAND + local_authority: NEWHAM + council_tax: 1_800 + savings: 0 + output: + # Para 30B(3) has no Universal Credit exception: the higher of the two, + # the worker's £22.61. The partner, with no earned income of her own, is + # exempt under para 30B's Universal Credit exemption. + newham_council_tax_reduction_individual_non_dep_deduction: [0, 1_175.72, 0] + newham_council_tax_reduction_non_dep_deductions: [1_175.72, 0] + council_tax_reduction: 84.28 + +- name: Westminster deducts for each member of a Universal Credit couple, as the Default Scheme does + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + worker: + age: 35 + is_claimant_or_partner: true + employment_income: 30_000 + weekly_hours: 37.5 + partner: + age: 35 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 10 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + couple: + members: [worker, partner] + universal_credit: 1_000 + households: + household: + members: [claimant, worker, partner] + country: ENGLAND + local_authority: WESTMINSTER + council_tax: 1_800 + savings: 0 + output: + # Westminster's working-age scheme is based on the Default Scheme, whose + # para 30(3) makes no single deduction where there is an award of + # Universal Credit. The worker pays £15.95 on the couple's joint £626.92 + # a week (Appendix A); the partner, earning but not in remunerative work, + # £5.20, and is not exempt, having earned income of her own. + westminster_council_tax_reduction_individual_non_dep_deduction: [0, 829.40, 270.40] + westminster_council_tax_reduction_non_dep_deductions: [1_099.80, 0] + council_tax_reduction: 700.20 + +- name: Westminster charges the applicant's own non-dependant son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + benunit: + members: [parent, son] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [parent, son] + country: ENGLAND + local_authority: WESTMINSTER + council_tax: 1_800 + savings: 0 + output: + westminster_council_tax_reduction_non_dep_deductions: 691.60 + council_tax_reduction: 1_108.40 + +- name: Westminster makes no deduction for a full-time student son in the applicant's benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + in_HE: true + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + benunit: + members: [parent, son] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [parent, son] + country: ENGLAND + local_authority: WESTMINSTER + council_tax: 1_800 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, true] + westminster_council_tax_reduction_non_dep_deductions: 0 + council_tax_reduction: 1_800 + +- name: Westminster exempts a non-dependant on income-based JSA but not the adult son in her benefit unit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + mother: + age: 40 + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + other_family: + members: [mother, son] + would_claim_uc: false + jsa_income: 1 + households: + household: + members: [claimant, mother, son] + country: ENGLAND + local_authority: WESTMINSTER + council_tax: 1_800 + savings: 0 + output: + westminster_council_tax_reduction_individual_non_dep_deduction: [0, 0, 691.60] + council_tax_reduction: 1_108.40 + +- name: Oxford charges the applicant's own non-dependant son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + benunit: + members: [parent, son] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [parent, son] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 0 + output: + # Para 43: £13.30 a week on £576.92; the claimant's own income band is + # the 100% band. + oxford_council_tax_reduction_non_dep_deductions: 691.60 + council_tax_reduction: 1_108.40 + +- name: Oxford deducts separately for a lone parent and her working son + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + mother: + age: 40 + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 20_000 + weekly_hours: 37.5 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + other_family: + members: [mother, son] + would_claim_uc: false + households: + household: + members: [claimant, mother, son] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 0 + output: + # Mother £5.20; son £10.60 on £384.62. + oxford_council_tax_reduction_individual_non_dep_deduction: [0, 270.40, 551.20] + council_tax_reduction: 978.40 + +- name: Oxford keeps deducting for a working-age non-dependant on Income Support + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + mother: + age: 40 + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + claimant_benunit: + members: [claimant] + would_claim_uc: false + claims_all_entitled_benefits: true + other_family: + members: [mother, son] + would_claim_uc: false + income_support: 1 + households: + household: + members: [claimant, mother, son] + country: ENGLAND + local_authority: OXFORD + council_tax: 1_800 + savings: 0 + output: + # Para 47: the benefit-receipt exemptions are for pensioner applicants + # only. + oxford_council_tax_reduction_individual_non_dep_deduction: [0, 270.40, 691.60] + council_tax_reduction: 838 + +# Para 9(2)(d)-(e) exclude the person, whatever family they are in: someone +# outside the household head's family who pays the householder as a boarder +# or lodger, and a joint tenant, are nobody's non-dependants. Para 9(3)(a)(i) +# keeps as a non-dependant a close relative who pays; with no relationship +# input, a member of the head's own family who pays board is taken to be one. + +- name: Merton makes no deduction for an adult son who himself pays as a lodger + period: 2026 + absolute_error_margin: 0.01 + input: + people: + applicant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + lodger: + age: 40 + is_claimant_or_partner: true + rent_paid_as_lodger: 3_000 + son: + age: 25 + is_claimant_or_partner: false + rent_paid_as_lodger: 3_000 + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + applicant_family: + members: [applicant] + would_claim_uc: false + claims_all_entitled_benefits: true + lodging_family: + members: [lodger, son] + would_claim_uc: false + households: + household: + members: [applicant, lodger, son] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + council_tax_reduction_individual_non_dep_deduction_eligible: [false, false, false] + merton_council_tax_reduction_non_dep_deductions: [0, 0] + council_tax_reduction: 1_800 + +- name: Merton charges a lodger's adult son who does not pay the householder himself + period: 2026 + absolute_error_margin: 0.01 + input: + people: + applicant: + age: 45 + is_household_head: true + is_claimant_or_partner: true + lodger: + age: 40 + is_claimant_or_partner: true + rent_paid_as_lodger: 3_000 + son: + age: 25 + is_claimant_or_partner: false + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + applicant_family: + members: [applicant] + would_claim_uc: false + claims_all_entitled_benefits: true + lodging_family: + members: [lodger, son] + would_claim_uc: false + households: + household: + members: [applicant, lodger, son] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # Para 9(2)(e) excludes only the person liable to pay the applicant. + council_tax_reduction_individual_non_dep_deduction_eligible: [false, false, true] + merton_council_tax_reduction_non_dep_deductions: [691.60, 0] + council_tax_reduction: 1_108.40 + +- name: Merton charges the applicant's own son who pays her board, as a close relative + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + rent_paid_as_boarder: 2_600 + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + benunit: + members: [parent, son] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [parent, son] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # Para 30: £13.30 a week on £576.92 (para 9(3)(a)(i) keeps him a + # non-dependant although he pays). + council_tax_reduction_individual_non_dep_deduction_eligible: [false, true] + merton_council_tax_reduction_non_dep_deductions: 691.60 + council_tax_reduction: 1_108.40 + +- name: Merton makes no deduction for an adult son who is himself a joint tenant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_household_head: true + is_claimant_or_partner: true + son: + age: 25 + is_claimant_or_partner: false + is_liable_for_household_rent: true + employment_income: 30_000 + weekly_hours: 37.5 + benunits: + benunit: + members: [parent, son] + would_claim_uc: false + claims_all_entitled_benefits: true + households: + household: + members: [parent, son] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # Para 9(2)(d): jointly and severally liable with the applicant. + council_tax_reduction_individual_non_dep_deduction_eligible: [false, false] + merton_council_tax_reduction_non_dep_deductions: 0 + +- name: Merton never charges the applicant as their own non-dependant + period: 2026 + absolute_error_margin: 0.01 + input: + people: + applicant: + age: 45 + is_household_head: true + young_mother: + age: 17 + is_parent: true + young_father: + age: 17 + is_parent: true + baby: + age: 0 + benunits: + benunit: + members: [applicant, young_mother, young_father, baby] + would_claim_uc: false + households: + household: + members: [applicant, young_mother, young_father, baby] + country: ENGLAND + local_authority: MERTON + council_tax: 1_800 + savings: 0 + output: + # The inference makes the young parents the benefit unit's couple, so + # the household head is not its claimant or partner; he is still the + # person liable for the council tax, and everyone else is under 18. + is_claimant_or_partner: [false, true, true, false] + council_tax_reduction_individual_non_dep_deduction_eligible: [false, false, false, false] + merton_council_tax_reduction_non_dep_deductions: 0 diff --git a/policyengine_uk/tests/test_benefit_unit_non_dependant_properties.py b/policyengine_uk/tests/test_benefit_unit_non_dependant_properties.py index 409abc406d..64e58ed21e 100644 --- a/policyengine_uk/tests/test_benefit_unit_non_dependant_properties.py +++ b/policyengine_uk/tests/test_benefit_unit_non_dependant_properties.py @@ -28,6 +28,23 @@ 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. Local schemes: in Merton, Kingston upon Thames, Newham, Westminster and + Oxford, each non-dependant's deduction and each claiming family's total + equal an independent calculation from the council's scale: a claimant or + partner banded on the couple's joint earnings and the couple deducted once + at the higher amount (each member in Merton, Kingston upon Thames and + Westminster when the couple has Universal Credit), any other adult banded + on their own earnings, each on their own hours; the benefit-receipt + exemptions for the person on the award, Universal Credit's tested on their + own earnings (none in Oxford); and the household's pool charged to each + claiming family by its share, less its own applicant, partner and + children, unless the applicant is exempt. Every other council's variables + are zero. Conservation: the claiming families together bear the pool + times the sum of their shares, which is the whole pool wherever the + shares sum to one (they follow the regulations' per-person wording, so a + couple jointly liable with others leaves its partner's share unborne). +6. Local independence: an adult in a benefit unit who is not its claimant or + partner changes no one else's local deduction through their earnings. """ import numpy as np @@ -587,3 +604,396 @@ def other_families(amounts): np.where(ctr_exempt, 0, v["claims"] * v["ctr_share"] * pool), atol=0.01, ) + + +# 5-6. The English working-age local schemes. Each council's weekly scale for +# 2026-27 (the parameters' first values, which also apply in 2025): Merton +# para 30, Kingston upon Thames para 30A, Newham para 30B, Westminster +# Appendix A and Oxford para 43. Westminster's working-age scheme is based on +# the Default Scheme (SI 2012/2886), whose para 30(3) deducts for each member +# of a couple with an award of Universal Credit, as Merton's and Kingston upon +# Thames's texts do. +LOCAL_SCHEMES = { + "MERTON": dict( + name="merton", + thresholds=[0, 279, 485, 605], + amounts=[5.20, 10.60, 13.30, 15.95], + benefit_exemptions=True, + each_uc_couple_member=True, + ), + "KINGSTON_UPON_THAMES": dict( + name="kingston_upon_thames", + thresholds=[0, 279, 485, 605], + amounts=[5.64, 11.50, 14.43, 17.31], + benefit_exemptions=True, + each_uc_couple_member=True, + ), + "NEWHAM": dict( + name="newham", + thresholds=[0, 183, 316, 394], + amounts=[7.54, 14.96, 18.84, 22.61], + benefit_exemptions=True, + each_uc_couple_member=False, + ), + "WESTMINSTER": dict( + name="westminster", + thresholds=[0, 279, 485, 605], + amounts=[5.20, 10.60, 13.30, 15.95], + benefit_exemptions=True, + each_uc_couple_member=True, + ), + "OXFORD": dict( + name="oxford", + thresholds=[0, 279, 485, 605], + amounts=[5.20, 10.60, 13.30, 15.95], + benefit_exemptions=False, + each_uc_couple_member=False, + ), +} +EARNINGS = [0, 2_600, 15_600, 30_000] +HOURS = [0, 10, 37.5] + + +@st.composite +def local_family(draw, role): + # Working-age adults; the head's family has the household's oldest + # member, below State Pension age, and every extra member is at most 60. + adult_age = st.integers(61, 65) if role == "head" else st.integers(25, 59) + n = draw(st.integers(1, 2)) + + def adults(strategy): + return draw(st.lists(strategy, min_size=n, max_size=n)) + + return dict( + role=role, + adult_ages=adults(adult_age), + adult_earnings=adults(st.sampled_from(EARNINGS)), + adult_hours=adults(st.sampled_from(HOURS)), + extras=draw(st.lists(extra_member(), min_size=0, max_size=2)), + extra_students=draw(st.lists(st.booleans(), min_size=2, max_size=2)), + child=draw(st.booleans()), + renter_pip=draw(st.booleans()), + payment=draw(st.floats(1_000, 10_000, allow_nan=False)), + would_claim_uc=draw(st.booleans()), + ) + + +@st.composite +def local_household(draw): + head = draw(local_family("head")) + others = [ + draw(local_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"])), + local_authority=draw(st.sampled_from(sorted(LOCAL_SCHEMES))), + ) + + +local_population = st.lists(local_household(), min_size=1, max_size=5) + + +def build_local(population, zero_extra_earnings=False): + """build(), plus each adult's earnings and hours, each person's education + (an extra member may be a full-time student), each family's Universal + Credit claim and each household's council in England.""" + situation, person_rows, family_rows = build(population) + people = list(situation["people"].values()) + row_iter = iter(zip(people, person_rows)) + for h, house in enumerate(population): + situation["households"][f"h{h}"].update( + country="ENGLAND", local_authority=house["local_authority"] + ) + for f, fam in enumerate(house["families"]): + situation["benunits"][f"h{h}_f{f}"]["would_claim_uc"] = fam[ + "would_claim_uc" + ] + members = ( + [("adult", i) for i in range(len(fam["adult_ages"]))] + + [("extra", i) for i in range(len(fam["extras"]))] + + ([("child", 0)] if fam["child"] else []) + ) + for kind, i in members: + person, row = next(row_iter) + if kind == "adult": + earnings = fam["adult_earnings"][i] + hours = fam["adult_hours"][i] + student = False + elif kind == "extra": + earnings = person["employment_income"] + if zero_extra_earnings: + earnings = 0.0 + hours = person["weekly_hours"] + student = fam["extra_students"][i] + else: + earnings, hours, student = 0.0, 0.0, False + person.update( + employment_income=float(earnings), + weekly_hours=float(hours), + current_education="TERTIARY" if student else "NOT_IN_EDUCATION", + ) + row.update( + earnings=float(earnings), + hours=float(hours), + student=student, + claimant_or_partner=kind == "adult", + local_authority=house["local_authority"], + ) + return situation, person_rows, family_rows + + +def weekly_scale(scheme, weekly_income): + amounts = [ + amount + for threshold, amount in zip(scheme["thresholds"], scheme["amounts"]) + if weekly_income >= threshold + ] + return amounts[-1] + + +def local_oracle(person_rows, family_rows, benefits, on_award, claims, ctr_share): + """Each person's deduction under their council's scheme and each family's + total, from the generated structure, the families' Universal Credit awards + and who is on each award (on_award, read from the model).""" + n_people, n_families = len(person_rows), len(family_rows) + individual = np.zeros(n_people) + counted = np.zeros(n_people) + for p, row in enumerate(person_rows): + f = row["family"] + scheme = LOCAL_SCHEMES[row["local_authority"]] + # Reg 9: a member of a family not liable for the rent, or an adult in + # any family's benefit unit who is not its applicant, partner, child + # or young person; aged 18 or over. Only a lodger and partner are + # liable to pay the householder (reg 9(2)(e)), so the lodger's + # qualifying young person is a non-dependant too. + eligible = row["age"] >= 18 and ( + family_rows[f]["role"] == "non_dependant" + or row["kind"] == "non_dependant" + or ( + family_rows[f]["role"] == "lodger" + and row["kind"] in ("dependant", "looked_after") + ) + ) + if not eligible or row["student"]: + continue + couple = [ + other + for other in person_rows + if other["family"] == f and other["claimant_or_partner"] + ] + if row["claimant_or_partner"]: + income = sum(member["earnings"] for member in couple) + else: + income = row["earnings"] + weekly = ( + weekly_scale(scheme, income / 52) + if row["hours"] >= 16 + else scheme["amounts"][0] + ) + exempt = False + if scheme["benefit_exemptions"]: + # Para 30(8)(a): the person the award is payable to. Para + # 30(8)(c): one entitled to Universal Credit, on their own + # earnings. + on_income_related = any( + on_award[b][p] + for b in ( + "income_support", + "income_based_jsa", + "income_related_esa", + "pension_credit", + ) + ) + exempt = on_income_related or ( + on_award["universal_credit"][p] and row["earnings"] <= 0 + ) + individual[p] = 0 if exempt else weekly * 52 + # One deduction for a couple, the higher, or each member's in Merton, + # Kingston upon Thames and Westminster where the couple has Universal + # Credit; any other member separately. + for f, family in enumerate(family_rows): + members = [p for p, row in enumerate(person_rows) if row["family"] == f] + couple = [p for p in members if person_rows[p]["claimant_or_partner"]] + scheme = LOCAL_SCHEMES[person_rows[members[0]]["local_authority"]] + each = scheme["each_uc_couple_member"] and benefits[f]["universal_credit"] > 0 + for p in members: + if p not in couple or each: + counted[p] = individual[p] + if couple and not each: + counted[couple[0]] = max(individual[p] for p in couple) + totals = np.zeros(n_families) + pools = np.zeros(n_families) + for f, family in enumerate(family_rows): + in_household = [ + p + for p, row in enumerate(person_rows) + if family_rows[row["family"]]["household"] == family["household"] + ] + own = [ + p + for p in in_household + if person_rows[p]["family"] == f + and person_rows[p]["kind"] != "non_dependant" + ] + pools[f] = sum(counted[p] for p in in_household) + pool = pools[f] - sum(counted[p] for p in own) + # Para 30(6): no deduction from an applicant who, or whose partner, + # gets a qualifying disability benefit; another applicant in the + # household is not exempt by it. + if not family["renter_pip"]: + totals[f] = claims[f] * ctr_share[f] * pool + return individual, totals, pools + + +def run_local(population, zero_extra_earnings=False): + situation, person_rows, family_rows = build_local(population, zero_extra_earnings) + sim = Simulation(situation=situation) + universal_credit = calc(sim, "universal_credit") + benefits = [ + {"universal_credit": universal_credit[f]} for f in range(len(family_rows)) + ] + on_award = { + b: calc(sim, f"is_on_{b}") > 0 + for b in ( + "income_support", + "income_based_jsa", + "income_related_esa", + "pension_credit", + "universal_credit", + ) + } + return sim, person_rows, family_rows, benefits, on_award + + +def legal_claims_and_shares(family_rows): + """Who claims and each claim's share, from the generated structure alone. + Where a family shares the rent, every family liable for it (the head's + and each sharer's) is jointly and severally liable for the council tax and + claims, each person liable bearing an equal share of the tax and of each + shared non-dependant's deduction (para 29(3)-(4), 30(5)); otherwise the + head's family claims alone, in full.""" + n = len(family_rows) + claims, shares = np.zeros(n), np.ones(n) + for h in {family["household"] for family in family_rows}: + families = [i for i, f in enumerate(family_rows) if f["household"] == h] + liable = [i for i in families if family_rows[i]["role"] in ("head", "sharer")] + shared = any(family_rows[i]["role"] == "sharer" for i in families) + liable_people = sum(family_rows[i]["n_liable"] for i in liable) + for i in families: + if shared: + claims[i] = i in liable + shares[i] = 1 / liable_people if i in liable else 1 + else: + claims[i] = family_rows[i]["role"] == "head" + return claims, shares + + +@PROPERTY_SETTINGS +@given(local_population) +def test_local_scheme_deductions_match_the_oracle(population): + sim, person_rows, family_rows, benefits, on_award = run_local(population) + claims, ctr_share = legal_claims_and_shares(family_rows) + # The claims and shares the totals use are the scheme's, not the model's. + assert np.array_equal(calc(sim, "council_tax_reduction_claimant_benunit"), claims) + assert np.allclose( + calc(sim, "council_tax_reduction_joint_liability_share"), ctr_share + ) + individual, totals, pools = local_oracle( + person_rows, family_rows, benefits, on_award, claims, ctr_share + ) + person_council = np.array([row["local_authority"] for row in person_rows]) + family_council = np.array( + [ + person_council[[r["family"] for r in person_rows].index(f)] + for f in range(len(family_rows)) + ] + ) + for council, scheme in LOCAL_SCHEMES.items(): + name = scheme["name"] + # 5. Each council's variables follow its own scheme and are zero + # elsewhere. + assert np.allclose( + calc(sim, f"{name}_council_tax_reduction_individual_non_dep_deduction"), + np.where(person_council == council, individual, 0), + atol=0.01, + ), name + assert np.allclose( + calc(sim, f"{name}_council_tax_reduction_non_dep_deductions"), + np.where(family_council == council, totals, 0), + atol=0.01, + ), name + # Conservation: no claiming family's own applicant, partner or child has a + # deduction, so where no applicant is exempt the claiming families of a + # household together bear its pool times the sum of their shares: the + # whole pool, each non-dependant once, where the shares sum to one. + family_totals = sum( + calc(sim, f"{scheme['name']}_council_tax_reduction_non_dep_deductions") + for scheme in LOCAL_SCHEMES.values() + ) + for h in {family["household"] for family in family_rows}: + families = [i for i, f in enumerate(family_rows) if f["household"] == h] + if any(family_rows[i]["renter_pip"] for i in families): + continue + borne = sum(family_totals[i] for i in families) + shares = sum(claims[i] * ctr_share[i] for i in families) + assert np.isclose(borne, shares * pools[families[0]], atol=0.01) + if np.isclose(shares, 1): + assert np.isclose(borne, pools[families[0]], atol=0.01) + + +def with_an_earning_adult_beside_its_claimant(population): + """Each household plus a family not liable for the rent whose claimant + works 37.5 hours without earnings beside a 22-year-old earning £30,000, + and an applicant with no exemption, so the property always binds.""" + family = dict( + role="non_dependant", + adult_ages=[40], + adult_earnings=[0], + adult_hours=[37.5], + extras=[ + dict( + kind="non_dependant", + age=22, + earnings=30_000, + hours=37.5, + attendance_allowance=False, + ) + ], + extra_students=[False, False], + child=False, + renter_pip=False, + payment=1_000.0, + would_claim_uc=False, + ) + return [ + dict( + house, + families=[dict(house["families"][0], renter_pip=False)] + + house["families"][1:] + + [family], + ) + for house in population + ] + + +@PROPERTY_SETTINGS +@given(local_population) +def test_local_scheme_in_unit_earnings_affect_only_their_own_deduction(population): + population = with_an_earning_adult_beside_its_claimant(population) + sim, person_rows, *_ = run_local(population) + without, *_ = run_local(population, zero_extra_earnings=True) + # 6. Zeroing the earnings of members who are not a claimant or partner + # leaves every claimant's and partner's deduction unchanged. + claimant_or_partner = np.array([row["claimant_or_partner"] for row in person_rows]) + for scheme in LOCAL_SCHEMES.values(): + variable = ( + f"{scheme['name']}_council_tax_reduction_individual_non_dep_deduction" + ) + assert np.allclose( + calc(sim, variable)[claimant_or_partner], + calc(without, variable)[claimant_or_partner], + atol=0.01, + ), variable 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 3488834bbe..6918815f77 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 @@ -1,4 +1,8 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.dwp.housing_benefit.non_dep_deduction._non_dependants import ( + deduction_per_family, + has_earned_income, +) from policyengine_uk.variables.household.demographic.highest_education import ( EducationType, ) @@ -10,8 +14,8 @@ def in_other_non_liable_family(person, period): 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.""" + The eligibility for a non-dependant deduction uses this, for the national + and local schemes alike.""" return ( ~person.benunit("council_tax_reduction_claimant_benunit", period) & ~person.benunit("liable_for_share_of_household_rent", period) @@ -131,39 +135,48 @@ def local_non_dep_deductions( individual_deduction_variable, one_deduction_for_uc_couples=True, ): - # 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. + """A claiming family's non-dependant deductions under one council's + scheme, in the shape of council_tax_reduction_non_dep_deductions. Every + eligible person in the household normally resides with each liable + applicant, so is a non-dependant of each claiming family (para 9 of each + scheme, the Default Scheme's wording): another family's adult, or an adult + in the claiming family's own benefit unit who is not its applicant, + partner or a child or young person.""" person = benunit.members - deductions = person(individual_deduction_variable, period) * ( - in_other_non_liable_family(person, period) - ) - deduction_for_benunit = benunit.max(deductions) + deductions = person(individual_deduction_variable, period) + # Only one deduction for a couple, the higher (para 30(3) of each scheme; + # Oxford para 44), except where the couple has an award of Universal + # Credit in the schemes that keep the Default Scheme's exception. Any + # other adult in a family is a non-dependant in their own right. + each_member_of_uc_couple = False if not one_deduction_for_uc_couples: - has_uc = benunit("universal_credit", period) > 0 - deduction_for_benunit = where( - has_uc, - benunit.sum(deductions), - deduction_for_benunit, - ) - is_benunit_head = benunit.members("is_benunit_head", period) - deductions_to_count = is_benunit_head * benunit.project(deduction_for_benunit) - deductions_in_household = benunit.max( - benunit.members.household.sum(deductions_to_count) + each_member_of_uc_couple = benunit("universal_credit", period) > 0 + family_amount = deduction_per_family( + benunit, period, deductions, each_member_of_uc_couple ) + # Each family's amount counted once. + counted = person("is_benunit_head", period) * benunit.project(family_amount) + in_household = benunit.max(person.household.sum(counted)) + # Members of the applicant's own family are not its non-dependants (para + # 9(2)(a)); the non-dependants in its benefit unit are. + in_unit = person("is_benefit_unit_non_dependant_for_council_tax_reduction", period) + own_in_unit = deduction_per_family( + benunit, period, deductions * in_unit, each_member_of_uc_couple + ) + non_dependants = in_household - family_amount + own_in_unit # 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 (para 30(5) of each scheme). share = benunit("council_tax_reduction_joint_liability_share", period) + # Only a family that claims bears deductions; a non-dependant's own family + # does not. + claims = benunit("council_tax_reduction_claimant_benunit", period) # No deduction from an applicant who, or whose partner, is blind or gets a # qualifying disability benefit, whatever other claimants in the household # get (the councils' schemes follow SI 2012/2886 Sch para 30(6)). applicant_exempt = benunit( "council_tax_reduction_applicant_has_non_dep_exemption", period ) - return where( - applicant_exempt, 0, (deductions_in_household - deduction_for_benunit) * share - ) + return where(applicant_exempt, 0, claims * share * non_dependants) def normal_gross_income_non_dep_deduction( @@ -196,39 +209,51 @@ def normal_gross_income_non_dep_deduction( # from income tax (Default Scheme Sch para 30(2), (9)). "spousal_maintenance_income", ] - earned_income_components = [ - "employment_income", - "self_employment_income", - ] gross_income = add(person, period, gross_income_components) - earned_income = add(person, period, earned_income_components) - weekly_benunit_gross_income = person.benunit.sum(gross_income) / WEEKS_IN_YEAR - weekly_benunit_earned_income = person.benunit.sum(earned_income) / WEEKS_IN_YEAR - benunit_weekly_hours = person.benunit.max(person("weekly_hours", period)) + # A couple's joint income (para 30(4) of each scheme; Oxford para 44); + # another adult in the family is a non-dependant in their own right, on + # their own income. + claimant_or_partner = person("is_claimant_or_partner", period) + couple_gross_income = person.benunit.sum(gross_income * claimant_or_partner) + weekly_gross_income = ( + where(claimant_or_partner, couple_gross_income, gross_income) / WEEKS_IN_YEAR + ) + # Remunerative work is each person's own (para 10 and para 30(1)(a)). A + # couple paying one deduction pays the higher, so the working member's + # band on their joint income. in_remunerative_work = ( - benunit_weekly_hours >= ctr.non_dep_deduction.remunerative_work_hours + person("weekly_hours", period) >= ctr.non_dep_deduction.remunerative_work_hours ) weekly_deduction = where( in_remunerative_work, - ctr.non_dep_deduction.amount.calc(weekly_benunit_gross_income), + ctr.non_dep_deduction.amount.calc(weekly_gross_income), ctr.non_dep_deduction.amount.calc(0), ) full_time_student = is_full_time_student_non_dep(person, period) - # No deduction for a non-dependant "who is on" Income Support, income-based - # JSA or income-related ESA: an award payable to them (Default Scheme Sch - # para 2(3)-(4)). Of a couple, only the payee is on the couple's award, and - # another member of their benefit unit's award never counts. + # No deduction for a non-dependant "who is on" Income Support, State + # Pension Credit, income-based JSA or income-related ESA (para 30(8)(a)). + # A person is on income-based JSA or income-related ESA when it is payable + # to them (Default Scheme Sch para 2(3)-(4)) and on Income Support when in + # receipt of it (para 2(1)); the model reads State Pension Credit the same + # way. Of a couple, only the payee is on the couple's award, and another + # member of their benefit unit is on it only through an award of their own. income_based_benefit = ( person("is_on_income_support", period) | person("is_on_income_based_jsa", period) | person("is_on_income_related_esa", period) - | (person.benunit("pension_credit", period) > 0) + | person("is_on_pension_credit", period) ) - has_uc = person.benunit("universal_credit", period) > 0 - no_earned_income = weekly_benunit_earned_income <= 0 + # Nor for one entitled to Universal Credit "calculated on the basis that + # the person does not have any earned income" (para 30(8)(c)): both joint + # claimants are entitled to a couple's award, and each is tested on their + # own earned income, as in the national schemes + # (council_tax_reduction_non_dep_deduction_exempt). + universal_credit_without_earned_income = person( + "is_on_universal_credit", period + ) & ~has_earned_income(person, period) exempt = ( full_time_student | (exempt_income_based_benefits & income_based_benefit) - | (exempt_uc_no_earned_income & has_uc & no_earned_income) + | (exempt_uc_no_earned_income & universal_credit_without_earned_income) ) return in_scheme_area * where(exempt, 0.0, weekly_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 b05ef2f086..6a8608c938 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 @@ -17,7 +17,17 @@ class council_tax_reduction_individual_non_dep_deduction_eligible(Variable): "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." + "applicant are not non-dependants. Those exclusions belong to the " + "person, whatever family they are in: a joint tenant liable for the " + "rent who is not their benefit unit's claimant or partner, and a " + "person outside the household head's family who pays the " + "householder for their own board or lodging, is nobody's " + "non-dependant. A member of the household head's own family who pays " + "board is taken to be a close relative of the head, so remains a " + "non-dependant. The model has no relationship input, so a close " + "relative in another family who pays board or lodging, or someone " + "paying on a non-commercial basis, is treated as excluded, although " + "the schemes make them non-dependants." ) definition_period = YEAR reference = ( @@ -37,4 +47,28 @@ def formula(person, period, parameters): in_unit = person( "is_benefit_unit_non_dependant_for_council_tax_reduction", period ) - return (person("age", period) >= 18) & (other_family | in_unit) + # Reg 9(2)(d)-(e) exclude the person, not only their family. A joint + # tenant (the same interest in the dwelling, LGFA 1992 s.6) is + # jointly and severally liable with the applicant, even one who is + # not their benefit unit's claimant or partner; the applicants and + # partners of liable families are never in_unit. Anyone who pays the + # householder for their own board or lodging is liable to make + # payments on a commercial basis, even where the rest of their family + # pays nothing, unless they are a close relative of the applicant or + # partner or the agreement is not commercial (reg 9(3)(a)). The model + # has no relationship input: a member of the household head's own + # family who pays board is taken to be a close relative, and anyone + # else who pays as excluded. + pays_householder = ( + add(person, period, ["rent_paid_as_boarder", "rent_paid_as_lodger"]) > 0 + ) + in_head_family = person.benunit("benunit_contains_household_head", period) + joint_tenant = person("is_liable_for_household_rent", period) & ~person( + "is_claimant_or_partner", period + ) + personally_excluded = joint_tenant | (pays_householder & ~in_head_family) + return ( + (person("age", period) >= 18) + & (other_family | in_unit) + & ~personally_excluded + ) diff --git a/policyengine_uk/variables/gov/local_authorities/westminster/council_tax_reduction/westminster_council_tax_reduction_non_dep_deductions.py b/policyengine_uk/variables/gov/local_authorities/westminster/council_tax_reduction/westminster_council_tax_reduction_non_dep_deductions.py index feefaa2813..7efecd22ff 100644 --- a/policyengine_uk/variables/gov/local_authorities/westminster/council_tax_reduction/westminster_council_tax_reduction_non_dep_deductions.py +++ b/policyengine_uk/variables/gov/local_authorities/westminster/council_tax_reduction/westminster_council_tax_reduction_non_dep_deductions.py @@ -8,12 +8,26 @@ class westminster_council_tax_reduction_non_dep_deductions(Variable): value_type = float entity = BenUnit label = "Westminster CTR non-dependent deductions" + documentation = ( + "Westminster's working-age scheme is based on the Council Tax " + "Reduction Schemes (Default Scheme) (England) Regulations 2012, and " + "its published summary sets out only the scale (Appendix A). The " + "Default Scheme's para 30(3) makes one deduction for a couple, the " + "higher, other than where there is an award of Universal Credit, so " + "each member of a non-dependant couple with Universal Credit is " + "deducted for, as in Merton's and Kingston upon Thames's texts." + ) definition_period = YEAR unit = GBP + reference = ( + "https://www.westminster.gov.uk/sites/default/files/media/documents/council-tax-support-scheme-for-2026-27.pdf", + "https://www.legislation.gov.uk/uksi/2012/2886/schedule/paragraph/30", + ) def formula(benunit, period, parameters): return local_non_dep_deductions( benunit, period, "westminster_council_tax_reduction_individual_non_dep_deduction", + one_deduction_for_uc_couples=False, )