From 6737dd502053c435d73e10ee4d6aad27188f9537 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 07:44:46 -0400 Subject: [PATCH 1/5] Take Housing Benefit earnings disregards from net earnings only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit housing_benefit_applicable_income_disregard gave every single person, couple and lone parent £5, £10 or £25 a week, and housing_benefit_applicable_income took it off total income, so pensioners with only a State Pension had £5 a week of it disregarded. The amounts were uprated with CPI (£5 became £7.02 a week in 2026). The additional earnings disregard was £37.10, also uprated, and went to anyone whose benefit unit's summed hours exceeded 30 (16 for lone parents). SI 2006/213 Sch 4 and SI 2006/214 Sch 4 (NI: SR 2006/405 and SR 2006/406 Sch 5) are "sums to be disregarded in the calculation of earnings". The disregards are now taken from the claimant's and partner's net earnings (housing_benefit_net_earnings: earnings less income tax, National Insurance and half of pension contributions, regs 36 and 38/39) and capped at them. The £5, £10 and £25 amounts have not changed since 2006 and are no longer uprated. The additional earnings disregard is £17.10 (£37.10 from 6 April 2020 to 4 April 2021 under SI 2020/371 reg 5), and it needs one of the work conditions in para 17(2) / 9(2) (one person aged at least 25 working at least 30 hours; 16 hours for lone parents, couples with a child and disabled workers) and net earnings at least equal to the other disregards, the childcare charges deducted and £17.10. A working-age claimant on Income Support, income-based JSA or income-related ESA has all earnings disregarded (SI 2006/213 Sch 4 para 12). Co-Authored-By: Claude Opus 5.5 --- changelog.d/hb-earnings-disregards.fixed.md | 1 + .../means_test/income_disregard/couple.yaml | 15 +- .../income_disregard/lone_parent.yaml | 17 +- .../means_test/income_disregard/single.yaml | 17 +- .../means_test/income_disregard/worker.yaml | 28 +- .../income_disregard/worker_age.yaml | 17 + .../income_disregard/worker_hours.yaml | 16 +- .../income_disregard/worker_hours_lower.yaml | 17 + .../pension_contribution_deduction_rate.yaml | 17 + ...g_benefit_applicable_income_disregard.yaml | 409 ++++++++++++++++++ .../housing_benefit_net_earnings.yaml | 102 +++++ ...itional_earnings_disregard_conditions.yaml | 154 +++++++ .../housing_benefit_earnings_disregards.yaml | 87 ++++ ...g_benefit_earnings_disregard_properties.py | 280 ++++++++++++ ...ing_benefit_applicable_income_disregard.py | 65 ++- .../housing_benefit_net_earnings.py | 59 +++ ...dditional_earnings_disregard_conditions.py | 54 +++ 17 files changed, 1309 insertions(+), 46 deletions(-) create mode 100644 changelog.d/hb-earnings-disregards.fixed.md create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_age.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/worker_hours_lower.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/pension_contribution_deduction_rate.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income_disregard.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_net_earnings.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/meets_housing_benefit_additional_earnings_disregard_conditions.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_earnings_disregards.yaml create mode 100644 policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py create mode 100644 policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_net_earnings.py create mode 100644 policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/meets_housing_benefit_additional_earnings_disregard_conditions.py diff --git a/changelog.d/hb-earnings-disregards.fixed.md b/changelog.d/hb-earnings-disregards.fixed.md new file mode 100644 index 000000000..47195fa63 --- /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..6a493f561 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,27 @@ -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 + 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 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..6eaf28022 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income_disregard.yaml @@ -0,0 +1,409 @@ +# 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: 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: 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 adds the additional disregard + 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: + housing_benefit_applicable_income_disregard: 1_149.20 # (£5 + £17.10) × 52 + +- 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: 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: 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..23f8ef5cd --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_net_earnings.yaml @@ -0,0 +1,102 @@ +# 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 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..fefc232b2 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_earnings_disregards.yaml @@ -0,0 +1,87 @@ +# 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. + +- name: Pensioner without earnings has no disregard against their State Pension + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + state_pension_reported: 15_000 + benunits: + benunit: + members: [person] + 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 + housing_benefit_applicable_amount: 13_312 # £256 × 52 + # 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: 70 + state_pension_reported: 15_000 + employment_income: 1_000 + benunits: + benunit: + members: [person] + 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. + housing_benefit: 3_716.57 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..e80ea7f93 --- /dev/null +++ b/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py @@ -0,0 +1,280 @@ +"""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 working-age claimant on Income Support, +income-based JSA or income-related ESA has all earnings disregarded (SI +2006/213 Sch 4 para 12). + +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. +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, a working-age family has +all its net earnings disregarded, while a pension-age family's disregard is +unchanged. Leaving Income Support can therefore lower the disregard as +earnings rise; that is the law, so invariant 3 holds those benefits at zero. +""" + +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_working_age_earnings(units): + without = calculate(units) + with_is = calculate(units, income_support=1_000) + key = "housing_benefit_applicable_income_disregard" + for i, unit in enumerate(units): + pension_age = any(adult["age"] >= 67 for adult in unit["adults"]) + if pension_age: + assert abs(with_is[key][i] - without[key][i]) < 0.01, unit + elif all(adult["age"] <= 60 for adult in unit["adults"]): + net = with_is["housing_benefit_net_earnings"][i] + assert abs(with_is[key][i] - net) < 0.01, unit 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 1722ced06..a696cfdbf 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,27 +5,56 @@ 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 working-age claimant on Income Support, income-based " + "Jobseeker's Allowance or income-related Employment and Support " + "Allowance has all earnings disregarded. 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", + "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 = add(benunit, period, ["weekly_hours"]) - # Calculate single, couple, lone parent, and worker disregards. - 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 + 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, ) - return weekly_disregard * WEEKS_IN_YEAR + 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 + ) + covers_additional = net_earnings >= standard + childcare + additional_amount + additional = where( + benunit( + "meets_housing_benefit_additional_earnings_disregard_conditions", period + ) + & covers_additional, + additional_amount, + 0, + ) + # Working age Sch 4 para 12 has no pension-age counterpart. 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 + ) + pension_age = benunit.any(benunit.members("is_SP_age", period)) + all_earnings = on_income_related_benefit & ~pension_age + return where(all_earnings, 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..c800e6d7d --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_net_earnings.py @@ -0,0 +1,59 @@ +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), less income tax, " + "Class 1, 2 and 4 National Insurance, and half of pension " + "contributions, floored at zero. The regulations deduct the income " + "tax deducted from, or notionally charged on, the earnings themselves; " + "PolicyEngine attributes a person's income tax to earnings in " + "proportion to their share of the person's total income. That is exact " + "when earnings are the person's only taxable income or no income tax " + "is due. Children's earnings are excluded (regulation 25(3); " + "pension age regulation 23(3))." + ) + definition_period = YEAR + unit = GBP + reference = ( + "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/214/regulation/36", + "https://www.legislation.gov.uk/uksi/2006/214/regulation/39", + ) + + def formula(benunit, period, parameters): + p = parameters(period).gov.dwp.housing_benefit.means_test + person = benunit.members + earnings = max_(person("employment_income", period), 0) + max_( + person("self_employment_income", period), 0 + ) + total_income = person("total_income", period) + earnings_share = np.divide( + earnings, + total_income, + out=np.zeros_like(earnings), + where=total_income > 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; the model's other Housing Benefit + # variables use the same proxy. + claimant_or_partner = person("is_adult", 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..566aea869 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/meets_housing_benefit_additional_earnings_disregard_conditions.py @@ -0,0 +1,54 @@ +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. The Working Tax Credit 30 hour element route (paragraph " + "17(2)(a) and 9(2)(a)) needs Working Tax Credit entitlement, which " + "requires one of these conditions, and 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." + ) + 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 + # The claimant and partner; the model's other Housing Benefit + # variables use the same proxy. + claimant_or_partner = person("is_adult", 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(~claimant_or_partner) + 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 From 8bed98831741817504aba556c402fd02b7d4082f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 08:08:09 -0400 Subject: [PATCH 2/5] Say what the Working Tax Credit route to the additional disregard adds The work-conditions variable said paragraph 17(2)(a) and 9(2)(a) need Working Tax Credit entitlement, which requires one of the modelled conditions. WTC Regs reg 4 also entitles a couple through a partner aged 60 working 16 hours, and its disability test differs from the disability premium's, so the route adds two narrow cases. Neither is modelled, and no tax credit can be claimed for 2025-26 onwards (SI 2014/1230 reg 6A(9)). Co-Authored-By: Claude Opus 5.5 --- ...efit_additional_earnings_disregard_conditions.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) 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 index 566aea869..77820dd5e 100644 --- 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 @@ -15,11 +15,14 @@ class meets_housing_benefit_additional_earnings_disregard_conditions(Variable): "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. The Working Tax Credit 30 hour element route (paragraph " - "17(2)(a) and 9(2)(a)) needs Working Tax Credit entitlement, which " - "requires one of these conditions, and 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 " + "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." ) definition_period = YEAR From 68c9df24b1915393daf38170186c47eb93ae5752 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 12:28:49 -0400 Subject: [PATCH 3/5] Grant the additional disregard at exact equality and harden its tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Para 17(1) and 9(1) add £17.10 where net earnings "equal or exceed" the other disregards, the childcare charges and £17.10. Net earnings are float32, so exactly £1,149.20 (£5 + £17.10 a week) was held as £1,149.19995 and failed the test. The comparison is now in pence, and new cases pin £1,149.20 (single, 30 hours) and £2,189.20 (lone parent, 16 hours) as granted and a penny below each as not. The end-to-end pension-age cases are aged 72 and set the applicable amount (£256 x 52) as an input. SI 2006/214 Sch 3 para 1(1) gives £256 only to a claimant who reached pensionable age before 1 April 2021, and £238 to one who reached it later; #1913 models that split. The working-age case asserts housing_benefit_entitlement, which #1910's 2026 abolition scaling does not change. The disabled-worker case is labelled as a model limitation: the disability premium also brings in Sch 4 para 3's £20 in place of £5, which is not modelled. A new case pins the is_adult proxy counting an 18-year-old young person's earnings and hours (the law excludes them) until #1896 replaces it. The net earnings documentation names the provisions that set the tax deducted (213 regs 36(3) and 39(1); 214 regs 36(2) and 40(1)), calls the proportional attribution a lower bound for employees, and says what the proxy counts. The changelog fragment is a bullet. Co-Authored-By: Claude Opus 5.5 --- changelog.d/hb-earnings-disregards.fixed.md | 2 +- ...g_benefit_applicable_income_disregard.yaml | 113 +++++++++++++++++- .../housing_benefit_earnings_disregards.yaml | 26 +++- ...g_benefit_earnings_disregard_properties.py | 6 +- ...ing_benefit_applicable_income_disregard.py | 7 +- .../housing_benefit_net_earnings.py | 29 +++-- ...dditional_earnings_disregard_conditions.py | 10 +- 7 files changed, 173 insertions(+), 20 deletions(-) diff --git a/changelog.d/hb-earnings-disregards.fixed.md b/changelog.d/hb-earnings-disregards.fixed.md index 47195fa63..1c7c15259 100644 --- a/changelog.d/hb-earnings-disregards.fixed.md +++ b/changelog.d/hb-earnings-disregards.fixed.md @@ -1 +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. +- 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/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 index 6eaf28022..f40eb7070 100644 --- 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 @@ -228,6 +228,38 @@ 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 @@ -264,6 +296,41 @@ # £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 @@ -344,7 +411,7 @@ # 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 adds the additional disregard +- 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: @@ -358,7 +425,17 @@ benunit: members: [person] output: - housing_benefit_applicable_income_disregard: 1_149.20 # (£5 + £17.10) × 52 + # 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 + # 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 @@ -392,6 +469,38 @@ housing_benefit_net_earnings: 0 housing_benefit_applicable_income_disregard: 0 +- name: An 18-year-old young person's earnings and hours count as a partner's (known limitation of the is_adult proxy) + 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: + # Known limitation, pinned so a change is deliberate. The model takes + # the claimant and partner to be members aged 18 or over (is_adult) + # until a claimant-or-partner variable replaces it (PolicyEngine/ + # policyengine-uk#1896). It counts the 18-year-old's £8,000 (below the + # tax and NI thresholds) as net earnings, treats the family as a couple + # with a child and one member working 16 hours, and £8,000 ≥ £520 + + # £889.20, so it gives (£10 + £17.10) × 52 = £1,409.20. + # In law 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. + housing_benefit_net_earnings: 8_000 + housing_benefit_applicable_income_disregard: 1_409.20 + - name: The coronavirus modification raised the additional disregard to £37.10 in 2020-21 period: 2020 absolute_error_margin: 0.01 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 index fefc232b2..9c7887b28 100644 --- 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 @@ -2,6 +2,15 @@ # 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 @@ -9,11 +18,14 @@ input: people: person: - age: 70 + 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] @@ -24,7 +36,6 @@ 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 - housing_benefit_applicable_amount: 13_312 # £256 × 52 # 6,240 − 0.65 × (14,514 − 13,312) = 6,240 − 781.30. housing_benefit: 5_458.70 @@ -34,12 +45,15 @@ input: people: person: - age: 70 + 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] @@ -83,5 +97,7 @@ # 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. - housing_benefit: 3_716.57 + # 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/test_housing_benefit_earnings_disregard_properties.py b/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py index e80ea7f93..48e0b4ef8 100644 --- a/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py +++ b/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py @@ -22,7 +22,11 @@ 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. + 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 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 a696cfdbf..0dfaabc0c 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 @@ -41,7 +41,12 @@ def formula(benunit, period, parameters): childcare = benunit( "housing_benefit_applicable_income_childcare_element", period ) - covers_additional = net_earnings >= standard + childcare + additional_amount + # "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 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 index c800e6d7d..34f2b107e 100644 --- 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 @@ -11,21 +11,33 @@ class housing_benefit_net_earnings(Variable): "income plus self-employment profit (each floored at zero, as a loss " "in one employment is not set against another), less income tax, " "Class 1, 2 and 4 National Insurance, and half of pension " - "contributions, floored at zero. The regulations deduct the income " - "tax deducted from, or notionally charged on, the earnings themselves; " - "PolicyEngine attributes a person's income tax to earnings in " - "proportion to their share of the person's total income. That is exact " - "when earnings are the person's only taxable income or no income tax " - "is due. Children's earnings are excluded (regulation 25(3); " - "pension age regulation 23(3))." + "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. 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)). The claimant and partner are proxied " + "by is_adult (aged 18 or over): earnings of members under 18 are " + "excluded, but those of a qualifying young person aged 18 or 19 are " + "counted, until a claimant-or-partner variable " + "(PolicyEngine/policyengine-uk#1896) replaces the proxy." ) definition_period = YEAR unit = GBP reference = ( "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/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): @@ -54,6 +66,7 @@ def formula(benunit, period, parameters): 0, ) # The claimant and partner; the model's other Housing Benefit - # variables use the same proxy. + # variables use the same proxy. It counts a qualifying young person + # aged 18 or 19, whose earnings the law excludes (#1896). claimant_or_partner = person("is_adult", 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 index 77820dd5e..cd17b5d3c 100644 --- 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 @@ -23,7 +23,12 @@ class meets_housing_benefit_additional_earnings_disregard_conditions(Variable): "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." + "housing_benefit_applicable_income_disregard. The claimant and " + "partner are proxied by is_adult (aged 18 or over): members under 18 " + "are treated as children, but a qualifying young person aged 18 or 19 " + "is treated as a claimant or partner, so their hours can meet a " + "condition, until a claimant-or-partner variable " + "(PolicyEngine/policyengine-uk#1896) replaces the proxy." ) definition_period = YEAR reference = ( @@ -37,7 +42,8 @@ def formula(benunit, period, parameters): p = parameters(period).gov.dwp.housing_benefit.means_test.income_disregard person = benunit.members # The claimant and partner; the model's other Housing Benefit - # variables use the same proxy. + # variables use the same proxy. It counts a qualifying young person + # aged 18 or 19 as an adult (#1896). claimant_or_partner = person("is_adult", period) hours = person("weekly_hours", period) works_hours = claimant_or_partner & (hours >= p.worker_hours) From 9baa82327b21b4683915775bc6ebbb64ef8982ad Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Thu, 1 Oct 2026 10:00:55 -0400 Subject: [PATCH 4/5] Date the additional earnings disregard back to 2006 and label known limitations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The £17.10 series started in 2010. Add the earlier sums from the point-in-time texts of SI 2006/213 Sch 4 para 17 and SI 2006/214 Sch 4 para 9: £14.50 as made, then £15.45, £16.05 and £16.85 from 1 April 2007, 2008 and 2009 (SI 2007/688, 2008/632, 2009/497), with the orders cited. Mark the two YAML cases that pin known model limitations with the value the law gives. Co-Authored-By: Claude Opus 5.5 --- .../means_test/income_disregard/worker.yaml | 15 +++++++++++++++ ...using_benefit_applicable_income_disregard.yaml | 6 ++++-- 2 files changed, 19 insertions(+), 2 deletions(-) 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 6a493f561..350e68ca4 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,5 +1,14 @@ 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: + # 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 2007/688 (£15.45), + # SI 2008/632 (£16.05), SI 2009/497 (£16.85) and SI 2010/793 (£17.10). + # Read from legislation.gov.uk point-in-time versions on 2026-10-01. + 2006-03-06: 14.5 + 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 @@ -19,6 +28,12 @@ metadata: 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 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 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 index f40eb7070..bf2d8e6c9 100644 --- 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 @@ -428,7 +428,8 @@ # 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 - # Intended model limitation. In law the same disability premium brings in + # 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 = @@ -487,7 +488,8 @@ benunit: members: [parent, young_person, child] output: - # Known limitation, pinned so a change is deliberate. The model takes + # KNOWN LIMITATION: the law gives 0. Pinned so a change is deliberate. + # The model takes # the claimant and partner to be members aged 18 or over (is_adult) # until a claimant-or-partner variable replaces it (PolicyEngine/ # policyengine-uk#1896). It counts the 18-year-old's £8,000 (below the From 97705be93da90eca00abc16170c8bcab765a15c4 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 04:44:49 -0400 Subject: [PATCH 5/5] =?UTF-8?q?Apply=20para=2012=20at=20any=20age,=20count?= =?UTF-8?q?=20statutory=20pay=20as=20earnings,=20add=20=C2=A314.90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From the round-2 review: - Para 12 has no age condition: SI 2006/213 applies to a claimant over the qualifying age whose partner is on IS, income-based JSA or income-related ESA (reg 5(1)(b); SI 2006/214 reg 5(2)). - Statutory sick and maternity pay are earnings (SI 2006/213 reg 35(1)(i); SI 2006/214 reg 35(1)(h)). - Income tax is attributed by the earnings' share of income before employment and self-employment losses, so a loss set against other income no longer drops the tax on the earnings. - The additional sum was £14.90 from April 2006 (SI 2006/645; SI 2007/688 arts 19(9) and 20(9) replace "£14.90"). Co-Authored-By: Claude Opus 5.5 --- .../means_test/income_disregard/worker.yaml | 13 ++++- ...g_benefit_applicable_income_disregard.yaml | 21 +++++++ .../housing_benefit_net_earnings.yaml | 58 +++++++++++++++++++ ...g_benefit_earnings_disregard_properties.py | 46 ++++++++++----- ...ing_benefit_applicable_income_disregard.py | 22 +++---- .../housing_benefit_net_earnings.py | 38 +++++++++--- 6 files changed, 162 insertions(+), 36 deletions(-) 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 350e68ca4..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 @@ -2,10 +2,15 @@ description: Housing Benefit increases the earnings disregard by this weekly amo values: # 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 2007/688 (£15.45), - # SI 2008/632 (£16.05), SI 2009/497 (£16.85) and SI 2010/793 (£17.10). - # Read from legislation.gov.uk point-in-time versions on 2026-10-01. + # 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 @@ -28,6 +33,8 @@ metadata: 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) 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 index bf2d8e6c9..280ea35ad 100644 --- 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 @@ -453,6 +453,27 @@ 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 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 index 23f8ef5cd..ab65b4965 100644 --- 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 @@ -100,3 +100,61 @@ 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). The model does not tax statutory pay. + 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 diff --git a/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py b/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py index 48e0b4ef8..c20932dfd 100644 --- a/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py +++ b/policyengine_uk/tests/test_housing_benefit_earnings_disregard_properties.py @@ -4,9 +4,10 @@ £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 working-age claimant on Income Support, -income-based JSA or income-related ESA has all earnings disregarded (SI -2006/213 Sch 4 para 12). +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: @@ -35,10 +36,14 @@ 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, a working-age family has -all its net earnings disregarded, while a pension-age family's disregard is -unchanged. Leaving Income Support can therefore lower the disregard as -earnings rise; that is the law, so invariant 3 holds those benefits at 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 @@ -271,14 +276,25 @@ def test_differential_against_the_old_flat_formula(units): @PROPERTY_SETTINGS @given(st.lists(families(), min_size=1, max_size=30)) -def test_income_support_disregards_all_working_age_earnings(units): - without = calculate(units) +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): - pension_age = any(adult["age"] >= 67 for adult in unit["adults"]) - if pension_age: - assert abs(with_is[key][i] - without[key][i]) < 0.01, unit - elif all(adult["age"] <= 60 for adult in unit["adults"]): - net = with_is["housing_benefit_net_earnings"][i] - assert abs(with_is[key][i] - net) < 0.01, unit + 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 0dfaabc0c..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 @@ -11,11 +11,12 @@ class housing_benefit_applicable_income_disregard(Variable): "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 working-age claimant on Income Support, income-based " - "Jobseeker's Allowance or income-related Employment and Support " - "Allowance has all earnings disregarded. The £20 disregards for " - "disabled people, carers and some part-time occupations are not " - "modelled." + "£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 @@ -55,11 +56,12 @@ def formula(benunit, period, parameters): additional_amount, 0, ) - # Working age Sch 4 para 12 has no pension-age counterpart. Families - # on Universal Credit cannot receive Housing Benefit in the model. + # 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 ) - pension_age = benunit.any(benunit.members("is_SP_age", period)) - all_earnings = on_income_related_benefit & ~pension_age - return where(all_earnings, net_earnings, standard + additional) + 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 index 34f2b107e..69d329cd3 100644 --- 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 @@ -9,7 +9,9 @@ class housing_benefit_net_earnings(Variable): "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), less income tax, " + "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); " @@ -17,7 +19,8 @@ class housing_benefit_net_earnings(Variable): "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. For employment income that is a lower bound " + "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 " @@ -32,9 +35,11 @@ class housing_benefit_net_earnings(Variable): 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", @@ -43,17 +48,30 @@ class housing_benefit_net_earnings(Variable): def formula(benunit, period, parameters): p = parameters(period).gov.dwp.housing_benefit.means_test person = benunit.members - earnings = max_(person("employment_income", period), 0) + max_( - person("self_employment_income", period), 0 + employment_income = person("employment_income", period) + self_employment_income = person("self_employment_income", period) + earnings = max_(employment_income, 0) + max_(self_employment_income, 0) + # Income tax is attributed by the earnings' share of total income + # 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) + + max_(-employment_income, 0) + + max_(-self_employment_income, 0) ) - total_income = person("total_income", period) earnings_share = np.divide( earnings, - total_income, + income_before_losses, out=np.zeros_like(earnings), - where=total_income > 0, + where=income_before_losses > 0, ) income_tax = person("income_tax", period) * min_(earnings_share, 1) + # Statutory sick and maternity pay are earnings (reg 35(1)(i); pension + # age reg 35(1)(h)). The model does not tax them. + statutory_pay = add( + person, period, ["statutory_sick_pay", "statutory_maternity_pay"] + ) national_insurance = add( person, period, ["ni_class_1_employee", "ni_class_2", "ni_class_4"] ) @@ -62,7 +80,11 @@ def formula(benunit, period, parameters): * p.pension_contribution_deduction_rate ) net_earnings = max_( - earnings - income_tax - national_insurance - pension_contributions, + earnings + + statutory_pay + - income_tax + - national_insurance + - pension_contributions, 0, ) # The claimant and partner; the model's other Housing Benefit