diff --git a/changelog.d/hb-earnings-disregards.fixed.md b/changelog.d/hb-earnings-disregards.fixed.md new file mode 100644 index 000000000..1c7c15259 --- /dev/null +++ b/changelog.d/hb-earnings-disregards.fixed.md @@ -0,0 +1 @@ +- Take the Housing Benefit earnings disregards from net earnings only, as SI 2006/213 and SI 2006/214 Schedule 4 require. The £5, £10 and £25 weekly disregards are now capped at the claimant's and partner's net earnings instead of being deducted from any income, including the State Pension, and they are no longer uprated with CPI. The additional earnings disregard is £17.10 (it was £37.10, uprated), and it now needs its work conditions (one person aged at least 25 working 30 hours, or 16 hours for lone parents, couples with a child and disabled workers) and net earnings covering the other disregards, childcare charges and £17.10. A working-age claimant on Income Support, income-based Jobseeker's Allowance or income-related Employment and Support Allowance has all earnings disregarded. diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/couple.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/couple.yaml index dae2090da..be57f4489 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/couple.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/couple.yaml @@ -1,11 +1,16 @@ -description: This amount of earnings is disreagrded for couples under the Housing Benefit. +description: Housing Benefit disregards this much of the combined weekly net earnings of a couple. values: - 2015-04-01: 10 + 2006-03-06: 10 metadata: period: week unit: currency-GBP label: Housing benefit couple earnings disregard - uprating: gov.benefit_uprating_cpi reference: - - title: The Housing Benefit Regulations 2006 Schedule 4 sec. 9 - href: https://www.legislation.gov.uk/uksi/2006/213/data.pdf#page=242 + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 7 + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4/paragraph/7 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006, Schedule 4 paragraph 7(b) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/4/paragraph/7 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 7 + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5/paragraph/7 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006, Schedule 5 paragraph 7(b) + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/5/paragraph/7 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/lone_parent.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/lone_parent.yaml index af70a4878..4a66e45ca 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/lone_parent.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/lone_parent.yaml @@ -1,11 +1,16 @@ -description: This amount of earnings is disreagrded for lone parents under the Housing Benefit. +description: Housing Benefit disregards this much of the weekly net earnings of a lone parent. values: - 2015-04-01: 25 + 2006-03-06: 25 metadata: period: week unit: currency-GBP label: Housing benefit lone parent earnings disregard - uprating: gov.benefit_uprating_cpi - reference: - - title: The Housing Benefit Regulations 2006 Schedule 4 sec. 4 - href: https://www.legislation.gov.uk/uksi/2006/213/data.pdf#page=240 + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 4 + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4/paragraph/4 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006, Schedule 4 paragraph 2 + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/4/paragraph/2 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 4 + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5/paragraph/4 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006, Schedule 5 paragraph 2 + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/5/paragraph/2 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/single.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/single.yaml index c16c86d81..a1ee19718 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/single.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/single.yaml @@ -1,11 +1,16 @@ -description: This amount of earnings is disreagrded for single filers under the Housing Benefit. +description: Housing Benefit disregards this much of the weekly net earnings of a claimant who has no partner and is not a lone parent. values: - 2015-04-01: 5 + 2006-03-06: 5 metadata: period: week unit: currency-GBP label: Housing benefit single person earnings disregard - uprating: gov.benefit_uprating_cpi - reference: - - title: The Housing Benefit Regulations 2006 Schedule 4 sec. 9 - href: https://www.legislation.gov.uk/uksi/2006/213/data.pdf#page=242 + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 10 + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4/paragraph/10 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006, Schedule 4 paragraph 7(a) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/4/paragraph/7 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 10 + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5/paragraph/10 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006, Schedule 5 paragraph 7(a) + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/5/paragraph/7 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker.yaml index c94028158..f353cddb4 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker.yaml @@ -1,11 +1,49 @@ -description: This amount of earnings is disreagrded for workers under the Housing Benefit. +description: Housing Benefit increases the earnings disregard by this weekly amount (the additional earnings disregard) where the claimant meets one of its work conditions and their net earnings at least equal the other earnings disregards, the deductible childcare charges and this amount. values: - 2015-04-01: 37.1 + # SI 2006/213 and 2006/214 as made (in force 6 March 2006): £14.50. Each + # up-rating order substituted the sum from 1 April (for specified + # purposes; the first Monday of April otherwise): SI 2006/645 (£14.90), + # SI 2007/688 (£15.45), SI 2008/632 (£16.05), SI 2009/497 (£16.85) and + # SI 2010/793 (£17.10). SI 2006/645 amended the provisions these + # Regulations consolidated, so legislation.gov.uk's revised text of SI + # 2006/213 and 2006/214 omits £14.90; SI 2007/688 arts 19(9) and 20(9) + # replace "£14.90". Read from legislation.gov.uk on 2026-10-01 and + # 2026-10-02. + 2006-03-06: 14.5 + 2006-04-01: 14.9 + 2007-04-01: 15.45 + 2008-04-01: 16.05 + 2009-04-01: 16.85 + 2010-04-01: 17.1 + # The Social Security (Coronavirus) (Further Measures) Regulations 2020 + # reg. 5 (NI: S.R. 2020/53 reg. 5) read £37.10 for £17.10 from 6 April 2020 + # until the end of 4 April 2021. + 2020-04-06: 37.1 + 2021-04-05: 17.1 metadata: period: week unit: currency-GBP label: Housing benefit worker earnings disregard - uprating: gov.benefit_uprating_cpi - reference: - - title: The Housing Benefit Regulations 2006 Schedule 4 (5)(6)(17) - href: https://www.legislation.gov.uk/uksi/2006/213/data.pdf#page=242 + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 17(1) and (3)(c) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4/paragraph/17 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006, Schedule 4 paragraph 9(1) and (3)(c) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/4/paragraph/9 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 17(1) and (3)(c) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5/paragraph/17 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006, Schedule 5 paragraph 9(1) and (3)(c) + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/5/paragraph/9 + - title: The Social Security Benefits Up-rating Order 2006, articles 19 and 20 + href: https://www.legislation.gov.uk/uksi/2006/645/article/19/made + - title: The Social Security Benefits Up-rating Order 2007, article 19(9) and 20(9) + href: https://www.legislation.gov.uk/uksi/2007/688/article/19 + - title: The Social Security Benefits Up-rating Order 2008, article 19(10) and 20(10) + href: https://www.legislation.gov.uk/uksi/2008/632/article/19 + - title: The Social Security Benefits Up-rating Order 2009, article 19(11) and 20(10) + href: https://www.legislation.gov.uk/uksi/2009/497/article/19 + - title: The Social Security Benefits Up-rating Order 2010, article 19(10) and 20(9) + href: https://www.legislation.gov.uk/uksi/2010/793/article/19 + - title: The Social Security (Coronavirus) (Further Measures) Regulations 2020, regulation 5 + href: https://www.legislation.gov.uk/uksi/2020/371/regulation/5 + - title: The Social Security (Coronavirus) (Further Measures) Regulations (Northern Ireland) 2020, regulation 5 + href: https://www.legislation.gov.uk/nisr/2020/53/regulation/5 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_age.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_age.yaml new file mode 100644 index 000000000..6bbc33009 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_age.yaml @@ -0,0 +1,17 @@ +description: Housing Benefit gives the additional earnings disregard for working the standard hours only to a claimant or partner of at least this age. +values: + 2006-03-06: 25 +metadata: + period: year + unit: year + label: Housing benefit worker element minimum age + economy: false + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 17(2)(b)(i) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4/paragraph/17 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006, Schedule 4 paragraph 9(2)(b)(i) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/4/paragraph/9 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 17(2)(b)(i) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5/paragraph/17 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006, Schedule 5 paragraph 9(2)(b)(i) + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/5/paragraph/9 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_hours.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_hours.yaml index 34814a8e7..b58da1ea6 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_hours.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_hours.yaml @@ -1,11 +1,17 @@ -description: Filers can receive an additional income disregard amount if the meet the following averge weekly work hour quota under the Housing Benefit. +description: Housing Benefit gives the additional earnings disregard to a claimant or partner aged at least the worker age who works on average at least this many hours a week. values: - 2015-04-01: 30 + 2006-03-06: 30 metadata: period: week unit: hour label: Housing benefit worker element hours requirement economy: false - reference: - - title: The Housing Benefit Regulations 2006 Schedule 4 sec. 17 (4) - href: https://www.legislation.gov.uk/uksi/2006/213/data.pdf#page=242 + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 17(2)(b)(i) and (4) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4/paragraph/17 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006, Schedule 4 paragraph 9(2)(b)(i) and (4) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/4/paragraph/9 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 17(2)(b)(i) and (4) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5/paragraph/17 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006, Schedule 5 paragraph 9(2)(b)(i) and (4) + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/5/paragraph/9 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_hours_lower.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_hours_lower.yaml new file mode 100644 index 000000000..fdc78a0d8 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_hours_lower.yaml @@ -0,0 +1,17 @@ +description: Housing Benefit gives the additional earnings disregard to a lone parent, a couple with a child or young person, or a disabled claimant or partner, where they work on average at least this many hours a week. +values: + 2006-03-06: 16 +metadata: + period: week + unit: hour + label: Housing benefit worker element lower hours requirement + economy: false + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 17(2)(b)(ii) to (v) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4/paragraph/17 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006, Schedule 4 paragraph 9(2)(b)(ii) to (iv) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/4/paragraph/9 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 17(2)(b)(ii) to (v) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5/paragraph/17 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006, Schedule 5 paragraph 9(2)(b)(ii) to (iv) + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/5/paragraph/9 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/pension_contribution_deduction_rate.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/pension_contribution_deduction_rate.yaml new file mode 100644 index 000000000..a43b60d27 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/pension_contribution_deduction_rate.yaml @@ -0,0 +1,17 @@ +description: Housing Benefit deducts this share of pension contributions when it calculates net earnings. +values: + 2006-03-06: 0.5 +metadata: + period: year + unit: /1 + label: Housing benefit net earnings pension contribution deduction rate + economy: false + reference: + - title: The Housing Benefit Regulations 2006, regulation 36(3)(b) and (c) + href: https://www.legislation.gov.uk/uksi/2006/213/regulation/36 + - title: The Housing Benefit Regulations 2006, regulation 38(1)(b)(ii) and (3)(c) + href: https://www.legislation.gov.uk/uksi/2006/213/regulation/38 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006, regulation 36(2)(b) and (c) + href: https://www.legislation.gov.uk/uksi/2006/214/regulation/36 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006, regulation 39(1)(b)(ii) and (2)(c) + href: https://www.legislation.gov.uk/uksi/2006/214/regulation/39 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income_disregard.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income_disregard.yaml new file mode 100644 index 000000000..95d447790 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income_disregard.yaml @@ -0,0 +1,534 @@ +# Housing Benefit earnings disregards: SI 2006/213 Sch 4 (working age) and +# SI 2006/214 Sch 4 (pension age); NI: SR 2006/405 and SR 2006/406 Sch 5. +# Weekly amounts: lone parent £25 (213 para 4; 214 para 2), couple £10 (213 +# para 7; 214 para 7(b)), anyone else £5 (213 para 10; 214 para 7(a)), each +# taken from net earnings, so never more than them. The additional earnings +# disregard of £17.10 (213 para 17; 214 para 9) needs a work condition and +# net earnings of at least the other disregards + deductible childcare +# charges + £17.10. Annual = weekly × 52. Earnings in these cases are below +# the income tax personal allowance (£12,570) and the National Insurance +# primary threshold (£241.73 × 52 = £12,569.96), so net earnings equal gross +# earnings. + +- name: Pensioner with only a State Pension has no earnings to disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + state_pension_reported: 15_000 + benunits: + benunit: + members: [person] + output: + housing_benefit_net_earnings: 0 + housing_benefit_applicable_income_disregard: 0 + +- name: Single person earning £3 a week has all £3 disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 156 # £3 × 52 + benunits: + benunit: + members: [person] + output: + housing_benefit_applicable_income_disregard: 156 # min(£5, £3) × 52 + +- name: Single person earning £100 a week has £5 disregarded in 2026 + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 5_200 # £100 × 52 + benunits: + benunit: + members: [person] + output: + housing_benefit_applicable_income_disregard: 260 # £5 × 52, not uprated + +- name: Single person earning £100 a week still has £5 disregarded in 2030 + period: 2030 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 5_200 + benunits: + benunit: + members: [person] + output: + housing_benefit_applicable_income_disregard: 260 # £5 × 52, not uprated + +- name: Couple has £10 disregarded from its combined earnings + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + employment_income: 5_200 + partner: + age: 38 + benunits: + benunit: + members: [claimant, partner] + output: + housing_benefit_applicable_income_disregard: 520 # £10 × 52 + +- name: Couple earning £3 and £4 a week has the combined £7 disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + employment_income: 156 # £3 × 52 + partner: + age: 38 + employment_income: 208 # £4 × 52 + benunits: + benunit: + members: [claimant, partner] + output: + # Para 7: the partner's earnings make up the £10 where the claimant's + # fall short. min(£10, £3 + £4) × 52 = 364. + housing_benefit_applicable_income_disregard: 364 + +- name: Lone parent earning £20 a week has all £20 disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 1_040 # £20 × 52 + weekly_hours: 10 + child: + age: 5 + benunits: + benunit: + members: [parent, child] + output: + housing_benefit_applicable_income_disregard: 1_040 # min(£25, £20) × 52 + +- name: Lone parent earning £100 a week has £25 disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 5_200 + weekly_hours: 10 + child: + age: 5 + benunits: + benunit: + members: [parent, child] + output: + housing_benefit_applicable_income_disregard: 1_300 # £25 × 52 + +- name: Worker aged 30 working 30 hours adds the £17.10 additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_000 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + # Net earnings £10,000 ≥ (£5 + £17.10) × 52 = £1,149.20. + housing_benefit_applicable_income_disregard: 1_149.20 + +- name: Worker aged exactly 25 working exactly 30 hours meets the condition + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 25 + employment_income: 10_000 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + # "aged at least 25" and "not less than 30 hours": (£5 + £17.10) × 52. + housing_benefit_applicable_income_disregard: 1_149.20 + +- name: Worker aged 24 working 30 hours without children has no additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 24 + employment_income: 10_000 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + housing_benefit_applicable_income_disregard: 260 # £5 × 52 + +- name: Worker aged 30 working 29 hours has no additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_000 + weekly_hours: 29 + benunits: + benunit: + members: [person] + output: + housing_benefit_applicable_income_disregard: 260 # £5 × 52 + +- name: Net earnings just below £5 plus £17.10 a week miss the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 1_149 # < (£5 + £17.10) × 52 = £1,149.20 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + housing_benefit_applicable_income_disregard: 260 # £5 × 52 + +- name: Net earnings just above £5 plus £17.10 a week get the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 1_150 # ≥ £1,149.20 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + housing_benefit_applicable_income_disregard: 1_149.20 # (£5 + £17.10) × 52 + +- name: Net earnings exactly equal to £5 plus £17.10 a week get the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 1_149.20 # (£5 + £17.10) × 52 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + # Para 17(1): net earnings "equal or exceed" £5 × 52 + £0 childcare + + # £17.10 × 52 = £1,149.20, so (£5 + £17.10) × 52. + housing_benefit_applicable_income_disregard: 1_149.20 + +- name: Net earnings a penny below £5 plus £17.10 a week miss the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 1_149.19 # £1,149.20 − £0.01 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + housing_benefit_applicable_income_disregard: 260 # £5 × 52 + +- name: Lone parent working 16 hours adds the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 8_000 + weekly_hours: 16 + child: + age: 5 + benunits: + benunit: + members: [parent, child] + output: + # £8,000 ≥ (£25 + £17.10) × 52 = £2,189.20. + housing_benefit_applicable_income_disregard: 2_189.20 + +- name: Lone parent working 16 hours whose earnings do not cover £25 plus £17.10 keeps only £25 + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 2_080 # £40 a week + weekly_hours: 16 + child: + age: 5 + benunits: + benunit: + members: [parent, child] + output: + # £2,080 < (£25 + £17.10) × 52 = £2,189.20. + housing_benefit_applicable_income_disregard: 1_300 # £25 × 52 + +- name: Lone parent working 16 hours whose net earnings exactly equal £25 plus £17.10 gets the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 2_189.20 # (£25 + £17.10) × 52 + weekly_hours: 16 + child: + age: 5 + benunits: + benunit: + members: [parent, child] + output: + # Para 17(1): £2,189.20 "equal or exceed" £1,300 + £0 childcare + £889.20. + housing_benefit_applicable_income_disregard: 2_189.20 # (£25 + £17.10) × 52 + +- name: Lone parent working 16 hours whose net earnings are a penny below £25 plus £17.10 keeps only £25 + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 2_189.19 # £2,189.20 − £0.01 + weekly_hours: 16 + child: + age: 5 + benunits: + benunit: + members: [parent, child] + output: + housing_benefit_applicable_income_disregard: 1_300 # £25 × 52 + +- name: Childcare charges count against the additional disregard's earnings test + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 5_200 # £100 a week + weekly_hours: 16 + childcare_expenses: 4_160 # £80 a week + child: + age: 5 + benunits: + benunit: + members: [parent, child] + output: + # Childcare deducted is min(£4,160, one-child maximum) = £4,160. + # £5,200 < £1,300 + £4,160 + £889.20 = £6,349.20, so only £25 a week. + housing_benefit_applicable_income_childcare_element: 4_160 + housing_benefit_applicable_income_disregard: 1_300 + +- name: Lone parent whose earnings cover the disregards and childcare gets the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 5_200 # £100 a week + weekly_hours: 16 + childcare_expenses: 1_040 # £20 a week + child: + age: 5 + benunits: + benunit: + members: [parent, child] + output: + # £5,200 ≥ £1,300 + £1,040 + £889.20 = £3,229.20. + housing_benefit_applicable_income_childcare_element: 1_040 + housing_benefit_applicable_income_disregard: 2_189.20 # (£25 + £17.10) × 52 + +- name: Couple with a child and one member working 16 hours adds the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 30 + employment_income: 8_000 + weekly_hours: 16 + partner: + age: 28 + child: + age: 5 + benunits: + benunit: + members: [claimant, partner, child] + output: + housing_benefit_applicable_income_disregard: 1_409.20 # (£10 + £17.10) × 52 + +- name: Childless couple each working 16 hours does not meet the 30 hour condition + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + employment_income: 4_000 + weekly_hours: 16 + partner: + age: 40 + employment_income: 4_000 + weekly_hours: 16 + benunits: + benunit: + members: [claimant, partner] + output: + # One member must work 30 hours; hours are not added across the couple. + housing_benefit_applicable_income_disregard: 520 # £10 × 52 + +- name: Disabled single person working 20 hours gets £5 plus the additional disregard (model limitation; the law gives £20 plus it) + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 8_000 + weekly_hours: 20 + is_disabled_for_benefits: true + benunits: + benunit: + members: [person] + output: + # Para 17(2)(b)(iv)(aa): a single claimant with the disability premium + # working 16 hours or more meets the condition. + meets_housing_benefit_additional_earnings_disregard_conditions: true + # KNOWN LIMITATION: the law gives £1,929.20. Intended model limitation. + # In law the same disability premium brings in + # the £20 disregard in place of £5: Sch 4 para 3(2), "This paragraph + # applies where the claimant's applicable amount includes an amount by + # way of the disability premium". The law gives (£20 + £17.10) × 52 = + # £1,929.20 (£8,000 ≥ £1,040 + £889.20). The £20 disregards are out of + # scope for this change and tracked as a follow-up; until then the model + # gives (£5 + £17.10) × 52. + housing_benefit_applicable_income_disregard: 1_149.20 + +- name: Working-age claimant on Income Support has all earnings disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 1_040 # £20 a week + benunits: + benunit: + members: [person] + income_support: 3_000 + output: + housing_benefit_applicable_income_disregard: 1_040 # SI 2006/213 Sch 4 para 12 + +- name: Pension-age claimant whose partner is on income-related ESA has all earnings disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + partner: + age: 50 + employment_income: 3_000 + benunits: + benunit: + members: [claimant, partner] + esa_income: 4_000 + output: + # SI 2006/213 reg 5(1)(b): the working-age Regulations apply to a + # claimant over the qualifying age whose partner is on income-related + # ESA, and with them Sch 4 para 12. 3,000 is below the tax and NI + # thresholds. (With the old age gate: 10 x 52 = 520.) + housing_benefit_applicable_income_disregard: 3_000 + +- name: A child's earnings are not the claimant's earnings + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + child: + age: 17 + employment_income: 2_000 + benunits: + benunit: + members: [parent, child] + output: + housing_benefit_net_earnings: 0 + housing_benefit_applicable_income_disregard: 0 + +- name: An 18-year-old young person's earnings and hours are not the claimant's + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + young_person: + age: 18 + employment_income: 8_000 + weekly_hours: 16 + current_education: UPPER_SECONDARY + child: + age: 5 + benunits: + benunit: + members: [parent, young_person, child] + output: + # A qualifying young person is a "young person" (SI 2006/213 reg + # 19(1)), whose income "shall not be treated as the income ... of the + # claimant" (reg 25(3)). The parent is a lone parent with no earnings, + # so net earnings and the disregard are 0. (Under the old is_adult proxy + # the model gave £8,000 and £1,409.20.) + housing_benefit_net_earnings: 0 + housing_benefit_applicable_income_disregard: 0 + +- name: The coronavirus modification raised the additional disregard to £37.10 in 2020-21 + period: 2020 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_000 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + # SI 2020/371 reg 5, 6 April 2020 to 4 April 2021: (£5 + £37.10) × 52. + housing_benefit_applicable_income_disregard: 2_189.20 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_net_earnings.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_net_earnings.yaml new file mode 100644 index 000000000..a7ab609c4 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_net_earnings.yaml @@ -0,0 +1,184 @@ +# Net earnings for the Housing Benefit earnings disregards: SI 2006/213 +# regs 36(3) and 38; SI 2006/214 regs 36(2) and 39. Gross earnings less income +# tax, National Insurance and half of pension contributions. PolicyEngine +# attributes a person's income tax to earnings in proportion to their share +# of the person's total income. 2026: personal allowance £12,570, basic rate +# 20%, Class 1 primary threshold £241.73 × 52 = £12,569.96, main rate 8%. + +- name: Earnings below the tax and National Insurance thresholds are all net earnings + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 5_200 + benunits: + benunit: + members: [person] + output: + housing_benefit_net_earnings: 5_200 + +- name: Half of pension contributions is deducted + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 5_200 + personal_pension_contributions: 400 + benunits: + benunit: + members: [person] + output: + housing_benefit_net_earnings: 5_000 # 5,200 − 0.5 × 400 + +- name: Income tax and National Insurance on earnings are deducted + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 20_000 + benunits: + benunit: + members: [person] + output: + # Income tax 0.2 × (20,000 − 12,570) = 1,486. + # NI 0.08 × (20,000 − 12,569.96) = 594.4032. + # 20,000 − 1,486 − 594.4032 = 17,919.5968. + housing_benefit_net_earnings: 17_919.60 + +- name: Income tax is attributed to earnings by their share of total income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + state_pension_reported: 15_000 + employment_income: 1_000 + benunits: + benunit: + members: [person] + output: + # Income tax 0.2 × (16,000 − 12,570) = 686, of which 1,000 / 16,000 is + # attributed to earnings: 42.875. No National Insurance above State + # Pension age. 1,000 − 42.875 = 957.125. + housing_benefit_net_earnings: 957.125 + +- name: A self-employment loss is not set against employment income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 2_000 + self_employment_income: -3_000 + benunits: + benunit: + members: [person] + output: + housing_benefit_net_earnings: 2_000 # SI 2006/213 reg 38(10) + +- name: A couple's net earnings are added together + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + employment_income: 3_000 + partner: + age: 38 + self_employment_income: 2_000 + benunits: + benunit: + members: [claimant, partner] + output: + housing_benefit_net_earnings: 5_000 + +- name: A self-employment loss set against other income does not drop the tax on employment income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 20_000 + self_employment_income: -25_000 + # Tax and NI as inputs: the test is about attribution, not the + # income tax calculation. + income_tax: 1_486 + ni_class_1_employee: 594.40 + benunits: + benunit: + members: [person] + output: + # Total income is 20,000 - 25,000 = -5,000. Before the loss it is + # 20,000, all earnings, so all the income tax is attributed to them: + # 20,000 - 1,486 - 594.40 = 17,919.60. (Dividing by the negative total + # income attributed none: 19,405.60.) + housing_benefit_net_earnings: 17_919.60 + +- name: Statutory maternity pay is earnings + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + statutory_maternity_pay: 8_000 + child: + age: 0 + benunits: + benunit: + members: [parent, child] + output: + # SI 2006/213 reg 35(1)(i). £8,000 is below the personal allowance and + # the NI primary threshold. + housing_benefit_net_earnings: 8_000 + # Lone parent: 25 x 52. + housing_benefit_applicable_income_disregard: 1_300 + +- name: Statutory sick pay is earnings at pension age + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + statutory_sick_pay: 2_000 + benunits: + benunit: + members: [person] + output: + # SI 2006/214 reg 35(1)(h). 5 x 52. + housing_benefit_net_earnings: 2_000 + housing_benefit_applicable_income_disregard: 260 + +- name: Income tax on statutory maternity pay is attributed to earnings + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_000 + statutory_maternity_pay: 20_000 + private_pension_income: 10_000 + income_tax: 5_486 + ni_class_1_employee: 1_394.40 + benunits: + benunit: + members: [person] + output: + # Taxable income 40,000: total_income 20,000 (employment and pension) + # plus 20,000 of statutory maternity pay, which the model taxes as + # employment income. Earnings are 10,000 + 20,000 = 30,000, so 3/4 of + # the 5,486 of income tax (4,114.50) is attributed to them. + # 30,000 - 4,114.50 - 1,394.40 = 24,491.10. + housing_benefit_net_earnings: 24_491.10 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/meets_housing_benefit_additional_earnings_disregard_conditions.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/meets_housing_benefit_additional_earnings_disregard_conditions.yaml new file mode 100644 index 000000000..a44009535 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/meets_housing_benefit_additional_earnings_disregard_conditions.yaml @@ -0,0 +1,154 @@ +# Work conditions for the additional earnings disregard: SI 2006/213 Sch 4 +# para 17(2); SI 2006/214 Sch 4 para 9(2). The net earnings test is in +# housing_benefit_applicable_income_disregard. + +- name: Single person aged 25 working 30 hours meets the condition + period: 2026 + input: + people: + person: + age: 25 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + meets_housing_benefit_additional_earnings_disregard_conditions: true + +- name: Single person aged 24 working 40 hours does not + period: 2026 + input: + people: + person: + age: 24 + weekly_hours: 40 + benunits: + benunit: + members: [person] + output: + meets_housing_benefit_additional_earnings_disregard_conditions: false + +- name: Couple where only the partner aged 24 works 30 hours does not + period: 2026 + input: + people: + claimant: + age: 26 + partner: + age: 24 + weekly_hours: 30 + benunits: + benunit: + members: [claimant, partner] + output: + # The member aged at least 25 must be the one working 30 hours. + meets_housing_benefit_additional_earnings_disregard_conditions: false + +- name: Lone parent working 16 hours meets the condition + period: 2026 + input: + people: + parent: + age: 20 + weekly_hours: 16 + child: + age: 2 + benunits: + benunit: + members: [parent, child] + output: + meets_housing_benefit_additional_earnings_disregard_conditions: true + +- name: Lone parent working 15 hours does not + period: 2026 + input: + people: + parent: + age: 30 + weekly_hours: 15 + child: + age: 2 + benunits: + benunit: + members: [parent, child] + output: + meets_housing_benefit_additional_earnings_disregard_conditions: false + +- name: Couple with a child and one member working 16 hours meets the condition + period: 2026 + input: + people: + claimant: + age: 22 + partner: + age: 22 + weekly_hours: 16 + child: + age: 1 + benunits: + benunit: + members: [claimant, partner, child] + output: + meets_housing_benefit_additional_earnings_disregard_conditions: true + +- name: Childless couple each working 20 hours does not + period: 2026 + input: + people: + claimant: + age: 40 + weekly_hours: 20 + partner: + age: 40 + weekly_hours: 20 + benunits: + benunit: + members: [claimant, partner] + output: + meets_housing_benefit_additional_earnings_disregard_conditions: false + +- name: Disabled partner working 16 hours meets the condition + period: 2026 + input: + people: + claimant: + age: 40 + partner: + age: 40 + weekly_hours: 16 + is_disabled_for_benefits: true + benunits: + benunit: + members: [claimant, partner] + output: + meets_housing_benefit_additional_earnings_disregard_conditions: true + +- name: Disabled partner not working and other partner working 16 hours does not + period: 2026 + input: + people: + claimant: + age: 40 + weekly_hours: 16 + partner: + age: 40 + is_disabled_for_benefits: true + benunits: + benunit: + members: [claimant, partner] + output: + # Para 17(2)(b)(v): the member working 16 hours must be the disabled one. + meets_housing_benefit_additional_earnings_disregard_conditions: false + +- name: Pensioner aged 70 working 30 hours meets the condition + period: 2026 + input: + people: + person: + age: 70 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + meets_housing_benefit_additional_earnings_disregard_conditions: true diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_earnings_disregards.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_earnings_disregards.yaml new file mode 100644 index 000000000..9c7887b28 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_earnings_disregards.yaml @@ -0,0 +1,103 @@ +# Housing Benefit with the earnings disregards, end to end (2026). +# Withdrawal rate 65% of income above the applicable amount. Council +# tenancies, so the Local Housing Allowance cannot apply, and no Guarantee +# Credit, so the Guarantee Credit passport cannot apply either. +# +# The pension-age cases set the applicable amount as an input, so they test +# the disregard independently of the personal allowance. SI 2006/214 Sch 3 +# para 1(1) gives a single claimant £256.00 a week if they "attained +# pensionable age before 1st April 2021" and £238.00 if they attained it "on +# or after 1st April 2021". The claimants here are aged 72, taken to have +# reached pensionable age before 1 April 2021, so £256 is their rate in law. +# The model does not yet split the allowance by that date; PolicyEngine/ +# policyengine-uk#1913 does. + +- name: Pensioner without earnings has no disregard against their State Pension + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 72 + state_pension_reported: 15_000 + benunits: + benunit: + members: [person] + # £256 × 52, the rate for a claimant who reached pensionable age + # before 1 April 2021 (SI 2006/214 Sch 3 para 1(1)). + housing_benefit_applicable_amount: 13_312 + households: + household: + members: [person] + rent: 6_240 + tenure_type: RENT_FROM_COUNCIL + output: + guarantee_credit: 0 # income above the £238 a week minimum guarantee + housing_benefit_applicable_income_disregard: 0 + # 15,000 − income tax 0.2 × (15,000 − 12,570) = 486 → 14,514. + housing_benefit_applicable_income: 14_514 + # 6,240 − 0.65 × (14,514 − 13,312) = 6,240 − 781.30. + housing_benefit: 5_458.70 + +- name: Pensioner with small earnings has £5 a week of them disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 72 + state_pension_reported: 15_000 + employment_income: 1_000 + benunits: + benunit: + members: [person] + # £256 × 52, the rate for a claimant who reached pensionable age + # before 1 April 2021 (SI 2006/214 Sch 3 para 1(1)). + housing_benefit_applicable_amount: 13_312 + households: + household: + members: [person] + rent: 6_240 + tenure_type: RENT_FROM_COUNCIL + output: + guarantee_credit: 0 + # Income tax 0.2 × (16,000 − 12,570) = 686; 1,000 / 16,000 of it on + # earnings, so net earnings 957.125 and the disregard is £5 × 52 = 260. + housing_benefit_net_earnings: 957.125 + housing_benefit_applicable_income_disregard: 260 + # 16,000 − 686 − 260 = 15,054. + housing_benefit_applicable_income: 15_054 + # 6,240 − 0.65 × (15,054 − 13,312) = 6,240 − 1,132.30. + housing_benefit: 5_107.70 + +- name: Working-age full-time worker continuing a Housing Benefit award gets the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 10_000 + weekly_hours: 30 + housing_benefit_reported: 1 + benunits: + benunit: + members: [person] + would_claim_uc: false + # Working age, so no Pension Credit; set so that no form of the + # Guarantee Credit passport can apply. + guarantee_credit: 0 + households: + household: + members: [person] + rent: 6_240 + tenure_type: RENT_FROM_COUNCIL + output: + housing_benefit_applicable_income_disregard: 1_149.20 # (£5 + £17.10) × 52 + # 10,000 is below the tax and NI thresholds: 10,000 − 1,149.20. + housing_benefit_applicable_income: 8_850.80 + housing_benefit_applicable_amount: 4_968.60 # £95.55 × 52 + # 6,240 − 0.65 × (8,850.80 − 4,968.60) = 6,240 − 2,523.43. The + # entitlement, not housing_benefit: PolicyEngine/policyengine-uk#1910 + # pays working-age awards in 2026 only up to their abolition date. + housing_benefit_entitlement: 3_716.57 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/legacy_claimant_relationships.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/legacy_claimant_relationships.yaml index 278930ea0..64f846910 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/legacy_claimant_relationships.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/legacy_claimant_relationships.yaml @@ -9,6 +9,10 @@ people: person0: age: 40 + # Earnings above the flat amount and below the tax and NI + # thresholds, so the disregard is the flat amount (Sch 4 caps it at + # net earnings). + employment_income: 2_000 weekly_hours: 0 is_claimant_or_partner: true is_parent: true @@ -40,6 +44,10 @@ people: person0: age: 40 + # Earnings above the flat amount and below the tax and NI + # thresholds, so the disregard is the flat amount (Sch 4 caps it at + # net earnings). + employment_income: 2_000 weekly_hours: 0 is_claimant_or_partner: true is_parent: true @@ -75,6 +83,10 @@ people: person0: age: 17 + # Earnings above the flat amount and below the tax and NI + # thresholds, so the disregard is the flat amount (Sch 4 caps it at + # net earnings). + employment_income: 2_000 weekly_hours: 0 is_claimant_or_partner: true benunits: @@ -101,6 +113,10 @@ people: person0: age: 30 + # Earnings above the flat amount and below the tax and NI + # thresholds, so the disregard is the flat amount (Sch 4 caps it at + # net earnings). + employment_income: 2_000 weekly_hours: 0 is_claimant_or_partner: true person1: @@ -131,6 +147,10 @@ people: person0: age: 17 + # Earnings above the flat amount and below the tax and NI + # thresholds, so the disregard is the flat amount (Sch 4 caps it at + # net earnings). + employment_income: 2_000 weekly_hours: 0 is_claimant_or_partner: true is_parent: true @@ -161,6 +181,10 @@ people: person0: age: 17 + # Earnings above the flat amount and below the tax and NI + # thresholds, so the disregard is the flat amount (Sch 4 caps it at + # net earnings). + employment_income: 2_000 weekly_hours: 0 is_claimant_or_partner: true person1: diff --git a/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py b/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py new file mode 100644 index 000000000..c20932dfd --- /dev/null +++ b/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py @@ -0,0 +1,300 @@ +"""Property-based tests for the Housing Benefit earnings disregards. + +SI 2006/213 Sch 4 (working age) and SI 2006/214 Sch 4 (pension age) disregard +£25 a week of a lone parent's net earnings, £10 of a couple's and £5 of anyone +else's, plus the £17.10 additional earnings disregard where a work condition +is met and net earnings at least equal the other disregards, the deductible +childcare charges and £17.10. A claimant who, or whose partner, is on +Income Support, income-based JSA or income-related ESA has all earnings +disregarded (SI 2006/213 Sch 4 para 12), at any age: SI 2006/213 then applies +whatever the claimant's age (reg 5(1)(b)). + +Invariants, for any generated population of families not on those benefits: + +1. Bounds: 0 <= disregard <= min(net earnings, (£25 + £17.10) x 52), and + net earnings never exceed the claimant's and partner's gross earnings. +2. No earnings, no disregard. +3. Metamorphic: the disregard is non-decreasing in employment income. +4. The weekly amounts are the same in every year from 2015 to 2030, except + the additional disregard in 2020-21, which SI 2020/371 reg 5 raised to + £37.10 (an intended difference). +5. The disregard reduces applicable income by no more than the claimant's + and partner's gross earnings, and never raises it. +6. Metamorphic: the pension-age and working-age schedules agree. Moving every + adult between working age (25 to 60) and pension age (67 to 90), with + earnings below the tax and National Insurance thresholds, leaves the + disregard unchanged. It covers only those earnings and adults aged 25 or + over, where nothing age-dependent (para 12, the age-25 test, National + Insurance above State Pension age) can differ between the two groups, so + it pins the current behaviour rather than independently testing the two + schedules. +7. Differential against the old flat formula (£5/£10/£25 by family type, plus + £37.10 for summed hours over 30, or 16 for lone parents), read without + CPI uprating. They agree for families whose net earnings cover the flat + amount and to whom neither formula gives the additional disregard. They + must differ where net earnings fall below the flat amount (the new + disregard is the net earnings) and for families without earnings (the new + disregard is zero). + +Para 12 is tested on its own: with Income Support, every family has all its +net earnings disregarded, at working or pension age. Leaving Income Support can +therefore lower the disregard as earnings rise; that is the law, so invariant +3 holds those benefits at zero. + +The additional sum's history is tested at each up-rating: £14.50 as made, +£14.90 from April 2006 (SI 2006/645), £15.45, £16.05, £16.85, then £17.10 +from April 2010, with SI 2020/371's £37.10 for 2020-21. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.system import system + +YEAR = 2026 +WEEKS = 52 +PROPERTY_SETTINGS = settings( + max_examples=5, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +# State Pension age is 66 until 2026-27 finishes phasing up; 67 and over is +# unambiguously pension age and 60 and under unambiguously working age. +ADULT_AGE = st.one_of(st.integers(18, 90), st.sampled_from([24, 25])) +SHAPES = { + "single": (1, 0), + "couple": (2, 0), + "lone_parent": (1, 2), + "couple_with_children": (2, 1), +} +HOURS = st.one_of( + st.sampled_from([0.0, 15.0, 16.0, 29.0, 30.0]), st.floats(0, 50, allow_nan=False) +) +EARNINGS = st.one_of( + st.just(0.0), + st.floats(0, 3_000, allow_nan=False), + st.floats(0, 60_000, allow_nan=False), +) +LOW_EARNINGS = st.one_of(st.just(0.0), st.floats(0, 12_000, allow_nan=False)) +DISREGARD = system.parameters(YEAR).gov.dwp.housing_benefit.means_test.income_disregard +MAX_WEEKLY = max(DISREGARD.single, DISREGARD.couple, DISREGARD.lone_parent) +MAX_ANNUAL = (MAX_WEEKLY + DISREGARD.worker) * WEEKS +# The flat amounts the old formula applied before CPI uprating. +OLD_FLAT = {"single": 5, "couple": 10, "lone_parent": 25, "couple_with_children": 10} +OLD_WORKER_HOURS = { + "single": 30, + "couple": 30, + "lone_parent": 16, + "couple_with_children": 30, +} + + +@st.composite +def adults(draw, earnings, age): + return dict( + age=draw(age), + employment_income=draw(earnings), + weekly_hours=draw(HOURS), + disabled=draw(st.booleans()), + pension_contributions=draw(st.one_of(st.just(0.0), st.floats(0, 2_000))), + ) + + +@st.composite +def families(draw, earnings=EARNINGS, age=ADULT_AGE): + shape = draw(st.sampled_from(sorted(SHAPES))) + n_adults, n_children = SHAPES[shape] + return dict( + shape=shape, + adults=[draw(adults(earnings, age)) for _ in range(n_adults)], + children=[draw(st.integers(0, 15)) for _ in range(n_children)], + childcare=( + draw(st.one_of(st.just(0.0), st.floats(0, 10_000))) if n_children else 0.0 + ), + ) + + +def situation(units, earnings_bump=0.0, income_support=0.0, age_map=None): + people, benunits, households = {}, {}, {} + for i, unit in enumerate(units): + names = [] + for j, adult in enumerate(unit["adults"]): + name = f"a{i}_{j}" + age = adult["age"] if age_map is None else age_map(adult["age"]) + people[name] = { + "age": {YEAR: age}, + "employment_income": { + YEAR: adult["employment_income"] + (earnings_bump if j == 0 else 0) + }, + "weekly_hours": {YEAR: adult["weekly_hours"]}, + "is_disabled_for_benefits": {YEAR: adult["disabled"]}, + "personal_pension_contributions": { + YEAR: adult["pension_contributions"] + }, + "childcare_expenses": {YEAR: unit["childcare"] if j == 0 else 0.0}, + } + names.append(name) + for k, child_age in enumerate(unit["children"]): + name = f"c{i}_{k}" + people[name] = {"age": {YEAR: child_age}} + names.append(name) + benunits[f"b{i}"] = { + "members": names, + "income_support": {YEAR: income_support}, + "jsa_income": {YEAR: 0.0}, + "esa_income": {YEAR: 0.0}, + } + households[f"h{i}"] = {"members": names} + return {"people": people, "benunits": benunits, "households": households} + + +VARIABLES = [ + "housing_benefit_applicable_income_disregard", + "housing_benefit_net_earnings", + "housing_benefit_applicable_income", + "housing_benefit_applicable_income_childcare_element", + "meets_housing_benefit_additional_earnings_disregard_conditions", +] + + +def calculate(units, **kwargs): + sim = Simulation(situation=situation(units, **kwargs)) + return {v: np.asarray(sim.calculate(v, YEAR), dtype=float) for v in VARIABLES} + + +def gross_earnings(unit): + return sum(max(adult["employment_income"], 0) for adult in unit["adults"]) + + +def old_flat_formula(unit): + """The formula this PR replaces, with its parameters left at their + 2015 values instead of CPI-uprated: a flat amount by family type plus + £37.10 where the family's summed hours exceed 30 (16 for lone parents).""" + hours = sum(adult["weekly_hours"] for adult in unit["adults"]) + worker = hours > OLD_WORKER_HOURS[unit["shape"]] + return (OLD_FLAT[unit["shape"]] + 37.1 * worker) * WEEKS + + +@PROPERTY_SETTINGS +@given(st.lists(families(), min_size=1, max_size=30)) +def test_disregard_is_bounded_by_net_earnings_and_the_statutory_maximum(units): + values = calculate(units) + disregard = values["housing_benefit_applicable_income_disregard"] + net = values["housing_benefit_net_earnings"] + for i, unit in enumerate(units): + assert disregard[i] >= 0, unit + assert disregard[i] <= min(net[i], MAX_ANNUAL) + 0.01, unit + assert net[i] <= gross_earnings(unit) + 0.01, unit + if gross_earnings(unit) == 0: + assert disregard[i] == 0, unit + + +@PROPERTY_SETTINGS +@given( + st.lists(families(), min_size=1, max_size=30), + st.floats(1, 5_000, allow_nan=False, allow_infinity=False), +) +def test_disregard_is_non_decreasing_in_earnings(units, bump): + before = calculate(units)["housing_benefit_applicable_income_disregard"] + after = calculate(units, earnings_bump=bump)[ + "housing_benefit_applicable_income_disregard" + ] + for i, unit in enumerate(units): + assert after[i] >= before[i] - 0.01, (unit, bump) + + +def test_weekly_amounts_are_frozen_from_2015_to_2030(): + for year in range(2015, 2031): + p = system.parameters(year).gov.dwp.housing_benefit.means_test.income_disregard + assert (p.single, p.couple, p.lone_parent) == (5, 10, 25), year + # SI 2020/371 reg 5: £37.10 from 6 April 2020 to 4 April 2021, which + # the model's fiscal-year convention reads as 2020. + expected_worker = 37.1 if year == 2020 else 17.1 + assert p.worker == expected_worker, year + + +@PROPERTY_SETTINGS +@given(st.lists(families(), min_size=1, max_size=30)) +def test_disregard_lowers_applicable_income_by_no_more_than_earnings(units): + values = calculate(units) + sim = Simulation(situation=situation(units)) + sim.set_input( + "housing_benefit_applicable_income_disregard", YEAR, np.zeros(len(units)) + ) + without = np.asarray(sim.calculate("housing_benefit_applicable_income", YEAR)) + with_disregard = values["housing_benefit_applicable_income"] + for i, unit in enumerate(units): + assert with_disregard[i] <= without[i] + 0.01, unit + assert without[i] - with_disregard[i] <= gross_earnings(unit) + 0.01, unit + + +@PROPERTY_SETTINGS +@given( + st.lists( + families(earnings=LOW_EARNINGS, age=st.integers(25, 60)), + max_size=30, + min_size=1, + ) +) +def test_pension_age_and_working_age_schedules_agree(units): + working_age = calculate(units) + # Map 25-60 onto 67-90, keeping everyone at or over 25. + pension_age = calculate(units, age_map=lambda age: 67 + (age - 25) * 23 // 35) + key = "housing_benefit_applicable_income_disregard" + for i, unit in enumerate(units): + assert abs(working_age[key][i] - pension_age[key][i]) < 0.01, unit + + +@PROPERTY_SETTINGS +@given(st.lists(families(), min_size=1, max_size=30)) +def test_differential_against_the_old_flat_formula(units): + values = calculate(units) + disregard = values["housing_benefit_applicable_income_disregard"] + net = values["housing_benefit_net_earnings"] + additional = values[ + "meets_housing_benefit_additional_earnings_disregard_conditions" + ].astype(bool) + for i, unit in enumerate(units): + old = old_flat_formula(unit) + flat = OLD_FLAT[unit["shape"]] * WEEKS + old_worker = old > flat + if gross_earnings(unit) == 0: + # Must differ: the old formula disregarded the flat amount from + # any income; the new one has no earnings to disregard. + assert disregard[i] == 0 < old, unit + elif net[i] < flat: + # Must differ: the disregard is capped at net earnings. + assert abs(disregard[i] - net[i]) < 0.01, unit + assert disregard[i] < old, unit + elif not old_worker and not additional[i]: + # Agree: the flat amount, without CPI uprating. + assert abs(disregard[i] - old) < 0.01, unit + + +@PROPERTY_SETTINGS +@given(st.lists(families(), min_size=1, max_size=30)) +def test_income_support_disregards_all_earnings_at_any_age(units): + with_is = calculate(units, income_support=1_000) + key = "housing_benefit_applicable_income_disregard" + for i, unit in enumerate(units): + net = with_is["housing_benefit_net_earnings"][i] + assert abs(with_is[key][i] - net) < 0.01, unit + + +def test_additional_disregard_history(): + expected = { + "2006-03-10": 14.5, + "2006-06-01": 14.9, + "2007-06-01": 15.45, + "2008-06-01": 16.05, + "2009-06-01": 16.85, + "2010-06-01": 17.1, + "2020-06-01": 37.1, + "2021-06-01": 17.1, + } + for instant, amount in expected.items(): + p = system.parameters(instant).gov.dwp.housing_benefit.means_test + assert p.income_disregard.worker == amount, instant diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_applicable_income_disregard.py b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_applicable_income_disregard.py index f2366fcd6..7a6c766d2 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_applicable_income_disregard.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_applicable_income_disregard.py @@ -5,36 +5,63 @@ class housing_benefit_applicable_income_disregard(Variable): value_type = float entity = BenUnit label = "Housing Benefit applicable income disregards" + documentation = ( + "Sums disregarded from the claimant's and partner's net earnings. " + "A lone parent has £25 a week, a couple £10 and anyone else £5, " + "capped at net earnings. The additional earnings disregard of £17.10 " + "is added where a work condition is met and net earnings at least " + "equal the other disregards, the childcare charges deducted and " + "£17.10. A claimant who, or whose partner, is on Income Support, " + "income-based Jobseeker's Allowance or income-related Employment and " + "Support Allowance has all earnings disregarded, at any age: the " + "working-age Regulations then apply (SI 2006/213 reg 5(1)(b)). The " + "£20 disregards for disabled people, carers and some part-time " + "occupations are not modelled." + ) definition_period = YEAR unit = GBP - reference = "https://www.legislation.gov.uk/uksi/2006/213/schedule/4" + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/schedule/4", + "https://www.legislation.gov.uk/uksi/2006/214/schedule/4", + "https://www.legislation.gov.uk/nisr/2006/405/schedule/5", + "https://www.legislation.gov.uk/nisr/2006/406/schedule/5", + ) def formula(benunit, period, parameters): - WTC = parameters(period).gov.dwp.tax_credits.working_tax_credit p = parameters(period).gov.dwp.housing_benefit.means_test.income_disregard - # Hours of the claimant and partner and, as the model did before, the - # legacy children or young persons; anyone else in the benefit unit - # does not count (Sch 4 para 17 tests the claimant's or partner's work). - person = benunit.members - members = person("is_claimant_or_partner", period) | person( - "is_child_or_young_person_for_legacy_benefits", period + net_earnings = benunit("housing_benefit_net_earnings", period) + # Working age Sch 4 paras 4, 7 and 10; pension age Sch 4 paras 2 and 7. + weekly_amount = select( + [benunit("is_lone_parent", period), benunit("is_couple", period)], + [p.lone_parent, p.couple], + default=p.single, ) - hours = add_for_members(benunit, period, ["weekly_hours"], members) - # The claimant categories are mutually exclusive (Sch 4 paras 4, 7, 10). - # Worker disregard is additional. - single = benunit("is_single_person", period) - single_disregard = single * p.single - couple = benunit("is_couple", period) - couple_disregard = couple * p.couple - lone_parent = benunit("is_lone_parent", period) - lone_parent_disregard = lone_parent * p.lone_parent - hour_requirement = where(lone_parent, WTC.min_hours.lower, p.worker_hours) - worker = hours > hour_requirement - worker_disregard = worker * p.worker - weekly_disregard = ( - single_disregard - + couple_disregard - + lone_parent_disregard - + worker_disregard + standard = min_(weekly_amount * WEEKS_IN_YEAR, net_earnings) + # Working age Sch 4 para 17; pension age Sch 4 para 9. + additional_amount = p.worker * WEEKS_IN_YEAR + childcare = benunit( + "housing_benefit_applicable_income_childcare_element", period ) - return weekly_disregard * WEEKS_IN_YEAR + # "Equal or exceed", compared in pence: net earnings are float32, so + # exactly £1,149.20 is held as £1,149.19995, and an exact comparison + # with the total fails at equality. + covers_additional = np.round(net_earnings.astype(float), 2) >= np.round( + (standard + childcare + additional_amount).astype(float), 2 + ) + additional = where( + benunit( + "meets_housing_benefit_additional_earnings_disregard_conditions", period + ) + & covers_additional, + additional_amount, + 0, + ) + # Working age Sch 4 para 12. The working-age Regulations apply at any + # age where the claimant or partner is on one of these benefits (SI + # 2006/213 reg 5(1)(b); SI 2006/214 reg 5(2)), so there is no age + # condition. Families on Universal Credit cannot receive Housing + # Benefit in the model. + on_income_related_benefit = ( + add(benunit, period, ["income_support", "jsa_income", "esa_income"]) > 0 + ) + return where(on_income_related_benefit, net_earnings, standard + additional) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_net_earnings.py b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_net_earnings.py new file mode 100644 index 000000000..e97c8f8b5 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_net_earnings.py @@ -0,0 +1,88 @@ +from policyengine_uk.model_api import * + + +class housing_benefit_net_earnings(Variable): + value_type = float + entity = BenUnit + label = "Housing Benefit net earnings" + documentation = ( + "Net earnings of the claimant and partner, from which the Housing " + "Benefit earnings disregards are taken. For each person: employment " + "income plus self-employment profit (each floored at zero, as a loss " + "in one employment is not set against another) plus statutory sick " + "and maternity pay (regulation 35(1)(i); pension age regulation " + "35(1)(h)), less income tax, " + "Class 1, 2 and 4 National Insurance, and half of pension " + "contributions, floored at zero. For an employee the regulations " + "deduct the income tax deducted from the earnings (regulation 36(3); " + "pension age regulation 36(2)); for the self-employed, a notional " + "basic-rate tax on the profit alone less personal reliefs (regulation " + "39(1); pension age regulation 40(1)). PolicyEngine attributes a " + "person's income tax to earnings in proportion to their share of the " + "person's total income before employment and self-employment losses. " + "For employment income that is a lower bound " + "on the tax on the earnings taxed as the top slice of income; for " + "self-employment profit alongside other income it can exceed the " + "notional tax. It is exact for an employee whose earnings are their " + "only taxable income, and whenever no income tax is due. The earnings " + "of a child or young person are not the claimant's (regulation 25(3); " + "pension age regulation 23(3)), so only the claimant's and partner's " + "count (is_claimant_or_partner)." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/regulation/35", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/36", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/38", + "https://www.legislation.gov.uk/uksi/2006/213/regulation/39", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/35", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/36", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/39", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/40", + ) + + def formula(benunit, period, parameters): + p = parameters(period).gov.dwp.housing_benefit.means_test + person = benunit.members + employment_income = person("employment_income", period) + self_employment_income = person("self_employment_income", period) + # Statutory sick and maternity pay are earnings (reg 35(1)(i); pension + # age reg 35(1)(h)). The model taxes them as employment income + # (employment_benefits) but leaves them out of total_income. + statutory_pay = person("employment_benefits", period) + earnings = ( + max_(employment_income, 0) + max_(self_employment_income, 0) + statutory_pay + ) + # Income tax is attributed by the earnings' share of total income, + # with statutory pay added and before any employment or + # self-employment loss, so that a loss set against other income for + # tax does not drop the tax on these earnings. + income_before_losses = ( + person("total_income", period) + + statutory_pay + + max_(-employment_income, 0) + + max_(-self_employment_income, 0) + ) + earnings_share = np.divide( + earnings, + income_before_losses, + out=np.zeros_like(earnings), + where=income_before_losses > 0, + ) + income_tax = person("income_tax", period) * min_(earnings_share, 1) + national_insurance = add( + person, period, ["ni_class_1_employee", "ni_class_2", "ni_class_4"] + ) + pension_contributions = ( + person("pension_contributions", period) + * p.pension_contribution_deduction_rate + ) + net_earnings = max_( + earnings - income_tax - national_insurance - pension_contributions, + 0, + ) + # The claimant and partner: a child's or young person's earnings are + # not the claimant's (reg 25(3); pension age reg 23(3)). + claimant_or_partner = person("is_claimant_or_partner", period) + return benunit.sum(net_earnings * claimant_or_partner) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/meets_housing_benefit_additional_earnings_disregard_conditions.py b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/meets_housing_benefit_additional_earnings_disregard_conditions.py new file mode 100644 index 000000000..c62844633 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/meets_housing_benefit_additional_earnings_disregard_conditions.py @@ -0,0 +1,60 @@ +from policyengine_uk.model_api import * + + +class meets_housing_benefit_additional_earnings_disregard_conditions(Variable): + value_type = bool + entity = BenUnit + label = ( + "meets a work condition for the Housing Benefit additional earnings disregard" + ) + documentation = ( + "Whether the claimant or partner meets one of the work conditions for " + "the additional earnings disregard: aged at least 25 and working at " + "least 30 hours a week; a couple with a child or young person, or a " + "lone parent, with someone working at least 16 hours; or a disabled " + "claimant or partner working at least 16 hours. Disability uses the " + "input the model uses for the disability premium. Membership of the " + "work-related activity group and the support component are not " + "modelled. Nor is the Working Tax Credit 30 hour element route " + "(paragraph 17(2)(a) and 9(2)(a)); beyond the routes above it covers " + "only a couple whose 30-hour worker is under 25 and whose partner is " + "aged at least 60 and works 16 hours, and a disability that meets the " + "Working Tax Credit test but not the disability premium one, and no " + "tax credit can be claimed for 2025-26 onwards. The 50 plus element " + "route (paragraph 17(2)(c) and 9(2)(c)) ended with the element on 6 " + "April 2012. The net earnings test is applied in " + "housing_benefit_applicable_income_disregard. Only the claimant's and " + "partner's hours count (is_claimant_or_partner), and the child or " + "young person test uses the legacy benefits definition " + "(is_child_or_young_person_for_legacy_benefits)." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/213/schedule/4/paragraph/17", + "https://www.legislation.gov.uk/uksi/2006/214/schedule/4/paragraph/9", + "https://www.legislation.gov.uk/nisr/2006/405/schedule/5/paragraph/17", + "https://www.legislation.gov.uk/nisr/2006/406/schedule/5/paragraph/9", + ) + + def formula(benunit, period, parameters): + p = parameters(period).gov.dwp.housing_benefit.means_test.income_disregard + person = benunit.members + claimant_or_partner = person("is_claimant_or_partner", period) + hours = person("weekly_hours", period) + works_hours = claimant_or_partner & (hours >= p.worker_hours) + works_lower_hours = claimant_or_partner & (hours >= p.worker_hours_lower) + # Para 17(2)(b)(i) and 9(2)(b)(i): one person meets both tests. + aged_worker = benunit.any(works_hours & (person("age", period) >= p.worker_age)) + # Para 17(2)(b)(ii)-(iii) and 9(2)(b)(ii)-(iii). + has_child = benunit.any( + person("is_child_or_young_person_for_legacy_benefits", period) + ) + family_with_child = (benunit("is_couple", period) & has_child) | benunit( + "is_lone_parent", period + ) + family_worker = family_with_child & benunit.any(works_lower_hours) + # Para 17(2)(b)(iv)-(v) and 9(2)(b)(iv): the disabled person works. + disabled_worker = benunit.any( + works_lower_hours & person("is_disabled_for_benefits", period) + ) + return aged_worker | family_worker | disabled_worker