From 271018f50084263dd1aab712266201e9ade85b1a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 16:19:30 -0400 Subject: [PATCH 1/6] Add the HB specified or temporary accommodation earnings disregard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SI 2006/213 Sch 4 para 18 (NI: SR 2006/405 Sch 5 para 18), in force from 5 October 2026 under SI 2026/753 reg 2(4) as substituted by SI 2026/978 reg 2(2), and NI SR 2026/157 reg 2(4). Adds the dated amounts, the in_specified_or_temporary_accommodation input (default false, same name and definition as #1910), the para 18 amount variable with its couple rule and working-age-only scope, and the para 17(3)(a) change that counts it in the £17.10 earnings test. YAML and Hypothesis tests. Co-Authored-By: Claude Opus 5.5 --- ...specified-accommodation-disregard.added.md | 1 + .../age_threshold/older.yaml | 18 + .../age_threshold/younger.yaml | 18 + .../couple/minors.yaml | 24 + .../couple/older.yaml | 24 + .../couple/younger.yaml | 24 + .../lone_parent/older.yaml | 24 + .../lone_parent/younger.yaml | 24 + .../single/older.yaml | 24 + .../single/younger.yaml | 24 + ..._or_temporary_accommodation_disregard.yaml | 680 ++++++++++++++++++ ...fied_accommodation_disregard_properties.py | 374 ++++++++++ ...ing_benefit_applicable_income_disregard.py | 38 +- ...ed_or_temporary_accommodation_disregard.py | 73 ++ ...in_specified_or_temporary_accommodation.py | 34 + 15 files changed, 1397 insertions(+), 7 deletions(-) create mode 100644 changelog.d/hb-specified-accommodation-disregard.added.md create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/age_threshold/older.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/age_threshold/younger.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/minors.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/older.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/younger.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/older.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/younger.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/older.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/younger.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml create mode 100644 policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py create mode 100644 policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.py create mode 100644 policyengine_uk/variables/gov/dwp/housing_benefit/in_specified_or_temporary_accommodation.py diff --git a/changelog.d/hb-specified-accommodation-disregard.added.md b/changelog.d/hb-specified-accommodation-disregard.added.md new file mode 100644 index 000000000..9020c37d8 --- /dev/null +++ b/changelog.d/hb-specified-accommodation-disregard.added.md @@ -0,0 +1 @@ +- Add the working-age Housing Benefit earnings disregard for claimants in specified or temporary accommodation (SI 2006/213 Schedule 4 paragraph 18, from 5 October 2026, inserted by SI 2026/753 as substituted by SI 2026/978; Northern Ireland: SR 2006/405 Schedule 5 paragraph 18, inserted by SR 2026/157). Where the claimant or partner is an employed or self-employed earner, £61.41 a week for a single claimant or lone parent under 25 and £77.73 at 25 or over; for a couple, £97.33 where both are under 18, £61.53 where one is 18 or over but both are under 25, and £119.70 where one is 25 or over. It is added to the £5, £10 and £25 disregards, capped at net earnings, and counts in the £17.10 additional disregard's earnings test (paragraph 17(3)(a)). It reads the new `in_specified_or_temporary_accommodation` input, which defaults to false, so it changes no dataset result. The model reads 2026-27 at 30 April 2026, so it applies from 2027-28. diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/age_threshold/older.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/age_threshold/older.yaml new file mode 100644 index 000000000..e13d3898c --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/age_threshold/older.yaml @@ -0,0 +1,18 @@ +description: Housing Benefit gives the higher specified or temporary accommodation earnings disregard to a single claimant or lone parent who has reached this age, and to a couple one of whom has. +values: + # Paragraph 18 has effect from 5 October 2026; its amounts are zero before. + 2026-10-05: 25 +metadata: + period: year + unit: year + label: Housing benefit specified or temporary accommodation earnings disregard older age threshold + economy: false + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 18(2)(a) to (c) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations 2026, regulation 2(4) as substituted by the Housing Benefit (Earned Income Disregards) (Amendment) (No. 2) Regulations 2026, regulation 2(2) + href: https://www.legislation.gov.uk/uksi/2026/978/regulation/2 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 18(2)(a) to (c) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations (Northern Ireland) 2026, regulation 2(4) + href: https://www.legislation.gov.uk/nisr/2026/157/regulation/2 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/age_threshold/younger.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/age_threshold/younger.yaml new file mode 100644 index 000000000..d8e2af0ae --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/age_threshold/younger.yaml @@ -0,0 +1,18 @@ +description: Housing Benefit gives a couple the specified or temporary accommodation earnings disregard at the minors' rate where neither member has reached this age, and at the younger couple rate where one has but neither has reached the older age threshold. +values: + # Paragraph 18 has effect from 5 October 2026; its amounts are zero before. + 2026-10-05: 18 +metadata: + period: year + unit: year + label: Housing benefit specified or temporary accommodation earnings disregard younger age threshold + economy: false + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 18(2)(c)(i) and (ii) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations 2026, regulation 2(4) as substituted by the Housing Benefit (Earned Income Disregards) (Amendment) (No. 2) Regulations 2026, regulation 2(2) + href: https://www.legislation.gov.uk/uksi/2026/978/regulation/2 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 18(2)(c)(i) and (ii) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations (Northern Ireland) 2026, regulation 2(4) + href: https://www.legislation.gov.uk/nisr/2026/157/regulation/2 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/minors.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/minors.yaml new file mode 100644 index 000000000..a2a6169f6 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/minors.yaml @@ -0,0 +1,24 @@ +description: Housing Benefit disregards this much of the weekly net earnings of a couple neither of whom has reached the younger age threshold, where the claimant lives in specified or temporary accommodation and the claimant or partner is an employed or self-employed earner. +values: + # Schedule 4 paragraph 18 (NI: Schedule 5 paragraph 18) has effect from 5 + # October 2026: SI 2026/753 reg. 2(4), as substituted by SI 2026/978 reg. + # 2(2), and NI SR 2026/157 reg. 2(4). There was no such disregard before. + # Neither instrument uprates the sums. The model reads each fiscal year at + # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it + # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards + # has the full amounts. + 2006-03-06: 0 + 2026-10-05: 97.33 +metadata: + period: week + unit: currency-GBP + label: Housing benefit specified or temporary accommodation earnings disregard, couple both under 18 + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 18(2)(c)(i) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations 2026, regulation 2(4) as substituted by the Housing Benefit (Earned Income Disregards) (Amendment) (No. 2) Regulations 2026, regulation 2(2) + href: https://www.legislation.gov.uk/uksi/2026/978/regulation/2 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 18(2)(c)(i) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations (Northern Ireland) 2026, regulation 2(4) + href: https://www.legislation.gov.uk/nisr/2026/157/regulation/2 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/older.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/older.yaml new file mode 100644 index 000000000..3c9bff884 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/older.yaml @@ -0,0 +1,24 @@ +description: Housing Benefit disregards this much of the weekly net earnings of a couple one of whom has reached the older age threshold, where the claimant lives in specified or temporary accommodation and the claimant or partner is an employed or self-employed earner. +values: + # Schedule 4 paragraph 18 (NI: Schedule 5 paragraph 18) has effect from 5 + # October 2026: SI 2026/753 reg. 2(4), as substituted by SI 2026/978 reg. + # 2(2), and NI SR 2026/157 reg. 2(4). There was no such disregard before. + # Neither instrument uprates the sums. The model reads each fiscal year at + # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it + # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards + # has the full amounts. + 2006-03-06: 0 + 2026-10-05: 119.7 +metadata: + period: week + unit: currency-GBP + label: Housing benefit specified or temporary accommodation earnings disregard, couple with one member 25 or over + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 18(2)(c)(iii) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations 2026, regulation 2(4) as substituted by the Housing Benefit (Earned Income Disregards) (Amendment) (No. 2) Regulations 2026, regulation 2(2) + href: https://www.legislation.gov.uk/uksi/2026/978/regulation/2 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 18(2)(c)(iii) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations (Northern Ireland) 2026, regulation 2(4) + href: https://www.legislation.gov.uk/nisr/2026/157/regulation/2 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/younger.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/younger.yaml new file mode 100644 index 000000000..b8c3ff090 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/younger.yaml @@ -0,0 +1,24 @@ +description: Housing Benefit disregards this much of the weekly net earnings of a couple one of whom has reached the younger age threshold but neither the older, where the claimant lives in specified or temporary accommodation and the claimant or partner is an employed or self-employed earner. +values: + # Schedule 4 paragraph 18 (NI: Schedule 5 paragraph 18) has effect from 5 + # October 2026: SI 2026/753 reg. 2(4), as substituted by SI 2026/978 reg. + # 2(2), and NI SR 2026/157 reg. 2(4). There was no such disregard before. + # Neither instrument uprates the sums. The model reads each fiscal year at + # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it + # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards + # has the full amounts. + 2006-03-06: 0 + 2026-10-05: 61.53 +metadata: + period: week + unit: currency-GBP + label: Housing benefit specified or temporary accommodation earnings disregard, couple both under 25, one 18 or over + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 18(2)(c)(ii) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations 2026, regulation 2(4) as substituted by the Housing Benefit (Earned Income Disregards) (Amendment) (No. 2) Regulations 2026, regulation 2(2) + href: https://www.legislation.gov.uk/uksi/2026/978/regulation/2 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 18(2)(c)(ii) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations (Northern Ireland) 2026, regulation 2(4) + href: https://www.legislation.gov.uk/nisr/2026/157/regulation/2 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/older.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/older.yaml new file mode 100644 index 000000000..8e1d8ec6c --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/older.yaml @@ -0,0 +1,24 @@ +description: Housing Benefit disregards this much of the weekly net earnings of a lone parent who has reached the older age threshold, where the claimant lives in specified or temporary accommodation and the claimant or partner is an employed or self-employed earner. +values: + # Schedule 4 paragraph 18 (NI: Schedule 5 paragraph 18) has effect from 5 + # October 2026: SI 2026/753 reg. 2(4), as substituted by SI 2026/978 reg. + # 2(2), and NI SR 2026/157 reg. 2(4). There was no such disregard before. + # Neither instrument uprates the sums. The model reads each fiscal year at + # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it + # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards + # has the full amounts. + 2006-03-06: 0 + 2026-10-05: 77.73 +metadata: + period: week + unit: currency-GBP + label: Housing benefit specified or temporary accommodation earnings disregard, lone parent aged 25 or over + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 18(2)(b)(ii) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations 2026, regulation 2(4) as substituted by the Housing Benefit (Earned Income Disregards) (Amendment) (No. 2) Regulations 2026, regulation 2(2) + href: https://www.legislation.gov.uk/uksi/2026/978/regulation/2 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 18(2)(b)(ii) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations (Northern Ireland) 2026, regulation 2(4) + href: https://www.legislation.gov.uk/nisr/2026/157/regulation/2 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/younger.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/younger.yaml new file mode 100644 index 000000000..ccf90edf3 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/younger.yaml @@ -0,0 +1,24 @@ +description: Housing Benefit disregards this much of the weekly net earnings of a lone parent under the older age threshold, where the claimant lives in specified or temporary accommodation and the claimant or partner is an employed or self-employed earner. +values: + # Schedule 4 paragraph 18 (NI: Schedule 5 paragraph 18) has effect from 5 + # October 2026: SI 2026/753 reg. 2(4), as substituted by SI 2026/978 reg. + # 2(2), and NI SR 2026/157 reg. 2(4). There was no such disregard before. + # Neither instrument uprates the sums. The model reads each fiscal year at + # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it + # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards + # has the full amounts. + 2006-03-06: 0 + 2026-10-05: 61.41 +metadata: + period: week + unit: currency-GBP + label: Housing benefit specified or temporary accommodation earnings disregard, lone parent under 25 + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 18(2)(b)(i) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations 2026, regulation 2(4) as substituted by the Housing Benefit (Earned Income Disregards) (Amendment) (No. 2) Regulations 2026, regulation 2(2) + href: https://www.legislation.gov.uk/uksi/2026/978/regulation/2 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 18(2)(b)(i) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations (Northern Ireland) 2026, regulation 2(4) + href: https://www.legislation.gov.uk/nisr/2026/157/regulation/2 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/older.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/older.yaml new file mode 100644 index 000000000..f40eaf46c --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/older.yaml @@ -0,0 +1,24 @@ +description: Housing Benefit disregards this much of the weekly net earnings of a single claimant (no partner, not a lone parent) who has reached the older age threshold, where the claimant lives in specified or temporary accommodation and the claimant or partner is an employed or self-employed earner. +values: + # Schedule 4 paragraph 18 (NI: Schedule 5 paragraph 18) has effect from 5 + # October 2026: SI 2026/753 reg. 2(4), as substituted by SI 2026/978 reg. + # 2(2), and NI SR 2026/157 reg. 2(4). There was no such disregard before. + # Neither instrument uprates the sums. The model reads each fiscal year at + # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it + # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards + # has the full amounts. + 2006-03-06: 0 + 2026-10-05: 77.73 +metadata: + period: week + unit: currency-GBP + label: Housing benefit specified or temporary accommodation earnings disregard, single claimant aged 25 or over + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 18(2)(a)(ii) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations 2026, regulation 2(4) as substituted by the Housing Benefit (Earned Income Disregards) (Amendment) (No. 2) Regulations 2026, regulation 2(2) + href: https://www.legislation.gov.uk/uksi/2026/978/regulation/2 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 18(2)(a)(ii) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations (Northern Ireland) 2026, regulation 2(4) + href: https://www.legislation.gov.uk/nisr/2026/157/regulation/2 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/younger.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/younger.yaml new file mode 100644 index 000000000..c04bfaed0 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/younger.yaml @@ -0,0 +1,24 @@ +description: Housing Benefit disregards this much of the weekly net earnings of a single claimant (no partner, not a lone parent) under the older age threshold, where the claimant lives in specified or temporary accommodation and the claimant or partner is an employed or self-employed earner. +values: + # Schedule 4 paragraph 18 (NI: Schedule 5 paragraph 18) has effect from 5 + # October 2026: SI 2026/753 reg. 2(4), as substituted by SI 2026/978 reg. + # 2(2), and NI SR 2026/157 reg. 2(4). There was no such disregard before. + # Neither instrument uprates the sums. The model reads each fiscal year at + # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it + # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards + # has the full amounts. + 2006-03-06: 0 + 2026-10-05: 61.41 +metadata: + period: week + unit: currency-GBP + label: Housing benefit specified or temporary accommodation earnings disregard, single claimant under 25 + reference: + - title: The Housing Benefit Regulations 2006, Schedule 4 paragraph 18(2)(a)(i) + href: https://www.legislation.gov.uk/uksi/2006/213/schedule/4 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations 2026, regulation 2(4) as substituted by the Housing Benefit (Earned Income Disregards) (Amendment) (No. 2) Regulations 2026, regulation 2(2) + href: https://www.legislation.gov.uk/uksi/2026/978/regulation/2 + - title: The Housing Benefit Regulations (Northern Ireland) 2006, Schedule 5 paragraph 18(2)(a)(i) + href: https://www.legislation.gov.uk/nisr/2006/405/schedule/5 + - title: The Housing Benefit (Earned Income Disregards) (Amendment) Regulations (Northern Ireland) 2026, regulation 2(4) + href: https://www.legislation.gov.uk/nisr/2026/157/regulation/2 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml new file mode 100644 index 000000000..4a6cb74cd --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml @@ -0,0 +1,680 @@ +# Housing Benefit earnings disregard for specified or temporary accommodation: +# SI 2006/213 Sch 4 para 18, inserted from 5 October 2026 by SI 2026/753 reg +# 2(4) as substituted by SI 2026/978 reg 2(2); NI: SR 2006/405 Sch 5 para 18, +# inserted from 5 October 2026 by SR 2026/157 reg 2(4). +# +# Para 18(1): where (a) the claimant lives in accommodation in UC Regs 2013 +# Sch 1 para 3A or 3B (NI: UC Regs 2016 Sch 1 para 4 or 4A) and (b) the +# claimant or partner is an employed or self-employed earner, the amount in +# para 18(2). A couple has one amount: it applies to the partner's earnings +# only to the extent the claimant's fall short of it. +# Para 18(2), weekly: single claimant or lone parent under 25 £61.41, 25 or +# over £77.73; couple both under 18 £97.33, one 18 or over but both under 25 +# £61.53, one 25 or over £119.70. +# It is added to the £5/£10/£25 (paras 10, 7, 4): no paragraph excludes the +# other. Para 17(3)(a) as amended counts it in the £17.10 additional +# disregard's earnings test: net earnings must equal or exceed the paras 3 to +# 10A and 18 disregards + deductible childcare + £17.10. +# +# Annual = weekly × 52: £61.41 → 3,193.32; £77.73 → 4,041.96; £97.33 → +# 5,061.16; £61.53 → 3,199.56; £119.70 → 6,224.40; £5 → 260; £10 → 520; £25 +# → 1,300; £17.10 → 889.20. +# +# The model reads each fiscal year at 30 April: 2027 (2027-28) has para 18 in +# full; 2026 (2026-27) reads 30 April 2026, before 5 October, so it has none. +# Earnings are below the income tax personal allowance and the National +# Insurance primary threshold (both £12,570), so net earnings equal gross +# earnings, and the hours are below every £17.10 work condition unless a +# test says otherwise. + +- name: Single claimant aged 30 in specified or temporary accommodation gets £5 plus £77.73 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_400 # £200 a week + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 # para 18(2)(a)(ii) + housing_benefit_applicable_income_disregard: 4_301.96 # (£5 + £77.73) × 52 + +- name: Single claimant aged 24 gets £5 plus £61.41 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 24 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 3_193.32 # para 18(2)(a)(i) + housing_benefit_applicable_income_disregard: 3_453.32 # (£5 + £61.41) × 52 + +- name: Single claimant aged exactly 25 has attained 25 and gets £77.73 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 25 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 + housing_benefit_applicable_income_disregard: 4_301.96 + +- name: Single claimant aged 17 is under 25 and gets £61.41 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 17 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + # Para 18(2)(a)(i) has no lower age band for a single claimant. + housing_benefit_specified_or_temporary_accommodation_disregard: 3_193.32 + housing_benefit_applicable_income_disregard: 3_453.32 + +- name: Lone parent aged 24 gets £25 plus £61.41 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 24 + employment_income: 10_400 + weekly_hours: 10 + child: + age: 5 + benunits: + benunit: + members: [parent, child] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 3_193.32 # para 18(2)(b)(i) + housing_benefit_applicable_income_disregard: 4_493.32 # (£25 + £61.41) × 52 + +- name: Lone parent aged 30 gets £25 plus £77.73 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 10_400 + weekly_hours: 10 + child: + age: 5 + benunits: + benunit: + members: [parent, child] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 # para 18(2)(b)(ii) + housing_benefit_applicable_income_disregard: 5_341.96 # (£25 + £77.73) × 52 + +- name: Couple both aged 17 gets £10 plus £97.33 + period: 2027 + absolute_error_margin: 0.01 + input: + # Roles supplied: without them the model treats a 16- or 17-year-old + # who is not the benefit-unit head as a dependent child. + people: + claimant: + age: 17 + is_claimant_or_partner: true + employment_income: 10_400 + weekly_hours: 10 + partner: + age: 17 + is_claimant_or_partner: true + benunits: + benunit: + members: [claimant, partner] + in_specified_or_temporary_accommodation: true + output: + is_couple: true + housing_benefit_specified_or_temporary_accommodation_disregard: 5_061.16 # para 18(2)(c)(i) + housing_benefit_applicable_income_disregard: 5_581.16 # (£10 + £97.33) × 52 + +- name: Couple aged 17 and 18 has one member who has attained 18, so £61.53 + period: 2027 + absolute_error_margin: 0.01 + input: + # Roles supplied: without them the model treats a 16- or 17-year-old + # who is not the benefit-unit head as a dependent child. + people: + claimant: + age: 18 + is_claimant_or_partner: true + employment_income: 10_400 + weekly_hours: 10 + partner: + age: 17 + is_claimant_or_partner: true + benunits: + benunit: + members: [claimant, partner] + in_specified_or_temporary_accommodation: true + output: + is_couple: true + housing_benefit_specified_or_temporary_accommodation_disregard: 3_199.56 # para 18(2)(c)(ii) + housing_benefit_applicable_income_disregard: 3_719.56 # (£10 + £61.53) × 52 + +- name: Couple aged 20 and 24 gets £10 plus £61.53 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 24 + employment_income: 10_400 + weekly_hours: 10 + partner: + age: 20 + benunits: + benunit: + members: [claimant, partner] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 3_199.56 + housing_benefit_applicable_income_disregard: 3_719.56 + +- name: Couple aged 24 and 25 has one member who has attained 25, so £119.70 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 24 + employment_income: 10_400 + weekly_hours: 10 + partner: + age: 25 + benunits: + benunit: + members: [claimant, partner] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 6_224.40 # para 18(2)(c)(iii) + housing_benefit_applicable_income_disregard: 6_744.40 # (£10 + £119.70) × 52 + +- name: Couple who both earn has one £119.70, not one each + period: 2027 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + employment_income: 10_400 + weekly_hours: 10 + partner: + age: 38 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [claimant, partner] + in_specified_or_temporary_accommodation: true + output: + # Para 18(1): it "shall not apply to their partner except where, and to + # the extent that" the claimant's disregarded earnings fall short. + housing_benefit_specified_or_temporary_accommodation_disregard: 6_224.40 + housing_benefit_applicable_income_disregard: 6_744.40 # (£10 + £119.70) × 52 + +- name: Couple whose claimant earns £100 and partner £50 a week has the £129.70 taken from both + period: 2027 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + employment_income: 5_200 # £100 a week + weekly_hours: 10 + partner: + age: 38 + employment_income: 2_600 # £50 a week + weekly_hours: 10 + benunits: + benunit: + members: [claimant, partner] + in_specified_or_temporary_accommodation: true + output: + # The claimant's £100 covers the £10 and £90 of the £119.70; the + # partner's earnings make up the other £29.70 (paras 7 and 18(1)). + housing_benefit_applicable_income_disregard: 6_744.40 # (£10 + £119.70) × 52 + +- name: Couple where only the partner works has the partner's earnings disregarded + period: 2027 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + partner: + age: 38 + employment_income: 2_600 # £50 a week + weekly_hours: 10 + benunits: + benunit: + members: [claimant, partner] + in_specified_or_temporary_accommodation: true + output: + # Para 18(1)(b): "the claimant or their partner" is an employed earner. + housing_benefit_specified_or_temporary_accommodation_disregard: 6_224.40 + # min(£10 + £119.70, £50) × 52: all £50 a week. + housing_benefit_applicable_income_disregard: 2_600 + +- name: Single claimant earning £50 a week has all £50 disregarded + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 2_600 # £50 a week + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 + housing_benefit_applicable_income_disregard: 2_600 # min(£5 + £77.73, £50) × 52 + +- name: Self-employed claimant is a self-employed earner and gets the disregard + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + self_employment_income: 5_200 # £100 a week, below the Class 4 lower profits limit + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_net_earnings: 5_200 + housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 + housing_benefit_applicable_income_disregard: 4_301.96 # (£5 + £77.73) × 52 + +- name: Claimant with no earner in the family gets no para 18 amount + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 0 # para 18(1)(b) fails + housing_benefit_applicable_income_disregard: 0 + +- name: Northern Ireland claimant gets the same amount under SR 2006/405 Sch 5 para 18 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + households: + household: + members: [person] + region: NORTHERN_IRELAND + output: + country: NORTHERN_IRELAND + housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 # SR 2026/157 + housing_benefit_applicable_income_disregard: 4_301.96 + +- name: Outside specified or temporary accommodation there is no para 18 amount + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: false + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 0 + housing_benefit_applicable_income_disregard: 260 # £5 × 52 + +- name: The input defaults to false + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [person] + output: + in_specified_or_temporary_accommodation: false + housing_benefit_specified_or_temporary_accommodation_disregard: 0 + housing_benefit_applicable_income_disregard: 260 + +- name: In 2026 the model reads 30 April 2026, before para 18 came into force + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + # KNOWN LIMITATION, intended: para 18 applies from 5 October 2026, for 183 + # of the 365 days of 2026-27, but the model reads the year's law at 30 + # April, so 2026-27 has no para 18 disregard. + housing_benefit_specified_or_temporary_accommodation_disregard: 0 + housing_benefit_applicable_income_disregard: 260 # £5 × 52 + +- name: In 2025 there is no para 18 disregard + period: 2025 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 0 + housing_benefit_applicable_income_disregard: 260 + +- name: In 2030 the amounts are unchanged, as nothing uprates them + period: 2030 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 + housing_benefit_applicable_income_disregard: 4_301.96 + +- name: Pension-age claimant has no para 18 amount, as SI 2006/214 is not amended + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + employment_income: 10_400 + weekly_hours: 10 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_pension_age_regulations_apply: true + housing_benefit_specified_or_temporary_accommodation_disregard: 0 + housing_benefit_applicable_income_disregard: 260 # SI 2006/214 Sch 4 para 7(a): £5 × 52 + +- name: Working-age claimant on Income Support still has all earnings disregarded + period: 2027 + 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 + in_specified_or_temporary_accommodation: true + output: + housing_benefit_applicable_income_disregard: 1_040 # Sch 4 para 12 + +# The £17.10 additional earnings disregard (para 17) and its earnings test, +# which from 5 October 2026 counts the para 18 amount (para 17(3)(a)). + +- name: Worker aged 30 working 30 hours whose net earnings exactly cover £5, £77.73 and £17.10 gets all three + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 5_191.16 # (£5 + £77.73 + £17.10) × 52 + weekly_hours: 30 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + meets_housing_benefit_additional_earnings_disregard_conditions: true + # Para 17(1): £5,191.16 "equal or exceed" £260 + £4,041.96 + £0 + £889.20. + housing_benefit_applicable_income_disregard: 5_191.16 + +- name: Worker aged 30 working 30 hours a penny short of £5, £77.73 and £17.10 loses the £17.10 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 5_191.15 + weekly_hours: 30 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + meets_housing_benefit_additional_earnings_disregard_conditions: true + housing_benefit_applicable_income_disregard: 4_301.96 # (£5 + £77.73) × 52 + +- name: Worker aged 30 working 30 hours earning £99 a week gets £5 and £77.73 but not £17.10 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 5_148 # £99 a week + weekly_hours: 30 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + # £5,148 < £260 + £4,041.96 + £889.20 = £5,191.16 (para 17(3)(a) "and 18"). + housing_benefit_applicable_income_disregard: 4_301.96 + +- name: The same worker outside specified or temporary accommodation gets £5 and £17.10 + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + employment_income: 5_148 + weekly_hours: 30 + benunits: + benunit: + members: [person] + output: + # £5,148 ≥ £260 + £889.20 = £1,149.20. + housing_benefit_applicable_income_disregard: 1_149.20 # (£5 + £17.10) × 52 + +- name: Lone parent working 16 hours whose earnings cover £25, £77.73 and £17.10 gets all three + period: 2027 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 7_800 # £150 a week + weekly_hours: 16 + child: + age: 5 + benunits: + benunit: + members: [parent, child] + in_specified_or_temporary_accommodation: true + output: + # £7,800 ≥ £1,300 + £4,041.96 + £0 + £889.20 = £6,231.16. + housing_benefit_applicable_income_disregard: 6_231.16 # (£25 + £77.73 + £17.10) × 52 + +- name: Childcare charges and the para 18 amount together can take the £17.10 away + period: 2027 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 7_800 # £150 a week + weekly_hours: 16 + childcare_expenses: 2_080 # £40 a week + child: + age: 5 + benunits: + benunit: + members: [parent, child] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_applicable_income_childcare_element: 2_080 + # £7,800 < £1,300 + £4,041.96 + £2,080 + £889.20 = £8,311.16. + housing_benefit_applicable_income_disregard: 5_341.96 # (£25 + £77.73) × 52 + +- name: Couple both aged 17 where one works 16 hours with a child gets £10, £97.33 and £17.10 + period: 2027 + absolute_error_margin: 0.01 + input: + # Roles supplied: without them the model treats a 16- or 17-year-old + # who is not the benefit-unit head as a dependent child. + people: + claimant: + age: 17 + is_claimant_or_partner: true + employment_income: 10_400 # £200 a week + weekly_hours: 16 + partner: + age: 17 + is_claimant_or_partner: true + child: + age: 1 + benunits: + benunit: + members: [claimant, partner, child] + in_specified_or_temporary_accommodation: true + output: + # Para 17(2)(b)(ii): a couple with a child, one member working 16 hours. + # £10,400 ≥ £520 + £5,061.16 + £889.20 = £6,470.36. + housing_benefit_applicable_income_disregard: 6_470.36 # (£10 + £97.33 + £17.10) × 52 + +# End to end: a working-age claimant continuing a Housing Benefit award, on a +# council tenancy (no Local Housing Allowance), with the applicable amount set +# as an input (£95.55 × 52, the 2026-27 single 25-or-over personal allowance) +# so the test does not depend on uprating it to 2027-28. Entitlement is the +# rent less 65% of applicable income above the applicable amount. + +- name: Housing Benefit entitlement rises by 65% of the para 18 amount + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 10_400 # £200 a week + weekly_hours: 10 + housing_benefit_reported: 1 + benunits: + benunit: + members: [person] + would_claim_uc: false + guarantee_credit: 0 + housing_benefit_applicable_amount: 4_968.60 + in_specified_or_temporary_accommodation: true + households: + household: + members: [person] + rent: 10_400 + tenure_type: RENT_FROM_COUNCIL + output: + housing_benefit_applicable_income_disregard: 4_301.96 # (£5 + £77.73) × 52 + housing_benefit_applicable_income: 6_098.04 # 10,400 − 4,301.96 + # 10,400 − 0.65 × (6,098.04 − 4,968.60) = 10,400 − 734.14. + housing_benefit_entitlement: 9_665.86 + +- name: The same claimant outside specified or temporary accommodation + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 40 + employment_income: 10_400 + weekly_hours: 10 + housing_benefit_reported: 1 + benunits: + benunit: + members: [person] + would_claim_uc: false + guarantee_credit: 0 + housing_benefit_applicable_amount: 4_968.60 + households: + household: + members: [person] + rent: 10_400 + tenure_type: RENT_FROM_COUNCIL + output: + housing_benefit_applicable_income_disregard: 260 + housing_benefit_applicable_income: 10_140 + # 10,400 − 0.65 × (10,140 − 4,968.60) = 10,400 − 3,361.41; 2,627.27 less + # than above, which is 0.65 × 4,041.96. + housing_benefit_entitlement: 7_038.59 diff --git a/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py b/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py new file mode 100644 index 000000000..dac050a39 --- /dev/null +++ b/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py @@ -0,0 +1,374 @@ +"""Property-based tests for the Housing Benefit earnings disregard for +specified or temporary accommodation. + +SI 2006/213 Sch 4 para 18 (NI: SR 2006/405 Sch 5 para 18), in force from 5 +October 2026 (SI 2026/753 reg 2(4) as substituted by SI 2026/978 reg 2(2); NI +SR 2026/157 reg 2(4)), disregards a weekly amount from the net earnings of a +claimant who lives in specified or temporary accommodation where the claimant +or partner is an employed or self-employed earner: £61.41 for a single +claimant or lone parent under 25, £77.73 at 25 or over; for a couple £97.33 +where both are under 18, £61.53 where one is 18 or over but both are under +25, and £119.70 where one is 25 or over. A couple has one amount. It is added +to the £5/£10/£25 disregards (paras 10, 7 and 4), and para 17(3)(a) counts it +in the £17.10 additional disregard's earnings test. The pension-age +Regulations (SI 2006/214; NI SR 2006/406) have no such disregard. + +Invariants, for 2027 (the first fiscal year the model reads with para 18 in +force), over generated populations of families not on Income Support, +income-based JSA or income-related ESA: + +1. Bounds: 0 <= para 18 amount <= £119.70 x 52, and it is one of the five + statutory amounts or zero; 0 <= total disregard <= min(net earnings, + (£25 + £119.70 + £17.10) x 52). +2. Zero when the input is false: no para 18 amount, and the total disregard + is what it was before para 18 (checked by setting the para 18 amount to + zero on the same population). +3. Metamorphic: with the input true, the total disregard is non-decreasing + in employment income. +4. Metamorphic: turning the input on never lowers the total disregard and + never raises applicable income. This holds because every para 18 amount + exceeds the £17.10 it can take away through para 17(3)(a); it is a + property of the amounts, not a rule of law. +5. Differential: the model agrees with an independent reference written from + the statutory text, with the para 18(2) amounts hard-coded from the + instrument rather than read from the parameters. +6. Pension age: families whose claimant and partner are all over State + Pension age get no para 18 amount. +7. Para 12: on Income Support, every family has all its net earnings + disregarded, with or without the input. +8. Dates: no para 18 amount in any year to 2026 (the model reads 2026-27 at + 30 April 2026, before 5 October), the same amounts in every year from + 2027 to 2035 (nothing uprates them), and the parameter files start on 5 + October 2026. +""" + +from pathlib import Path + +import numpy as np +import yaml +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.system import system + +YEAR = 2027 +WEEKS = 52 +PROPERTY_SETTINGS = settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +# Para 18(2), weekly, from the instrument (SI 2026/978 reg 2(2)). +SINGLE_UNDER_25, SINGLE_25_PLUS = 61.41, 77.73 +COUPLE_BOTH_UNDER_18, COUPLE_UNDER_25, COUPLE_25_PLUS = 97.33, 61.53, 119.70 +STATUTORY_WEEKLY = { + SINGLE_UNDER_25, + SINGLE_25_PLUS, + COUPLE_BOTH_UNDER_18, + COUPLE_UNDER_25, + COUPLE_25_PLUS, +} +# Paras 4, 7 and 10, and para 17. +BASE_WEEKLY = {"single": 5, "lone_parent": 25, "couple": 10, "couple_with_children": 10} +ADDITIONAL_WEEKLY = 17.10 +MAX_ANNUAL = (25 + COUPLE_25_PLUS + ADDITIONAL_WEEKLY) * WEEKS + +SHAPES = { + "single": (1, 0), + "couple": (2, 0), + "lone_parent": (1, 2), + "couple_with_children": (2, 1), +} +# Working age includes the para 18(2) age boundaries; pension age is over +# State Pension age in 2027. Families are wholly one or the other. +WORKING_AGE = st.one_of(st.integers(16, 60), st.sampled_from([17, 18, 24, 25])) +PENSION_AGE = st.integers(67, 90) +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, 12_000, allow_nan=False), + st.floats(0, 60_000, allow_nan=False), +) +SELF_EMPLOYMENT = st.one_of(st.just(0.0), st.floats(0, 12_000, allow_nan=False)) + + +@st.composite +def adults(draw, age): + return dict( + age=draw(age), + employment_income=draw(EARNINGS), + self_employment_income=draw(SELF_EMPLOYMENT), + weekly_hours=draw(HOURS), + disabled=draw(st.booleans()), + ) + + +@st.composite +def families(draw, pension_age=None, accommodation=None): + shape = draw(st.sampled_from(sorted(SHAPES))) + n_adults, n_children = SHAPES[shape] + if pension_age is None: + pension_age = draw(st.booleans()) + age = PENSION_AGE if pension_age else WORKING_AGE + return dict( + shape=shape, + pension_age=pension_age, + accommodation=(draw(st.booleans()) if accommodation is None else accommodation), + adults=[draw(adults(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, accommodation=None): + people, benunits, households = {}, {}, {} + for i, unit in enumerate(units): + names = [] + for j, adult in enumerate(unit["adults"]): + name = f"a{i}_{j}" + people[name] = { + "age": {YEAR: adult["age"]}, + # Roles supplied: the model would otherwise treat a 16- or + # 17-year-old non-head as a dependant, and a partner 20 or + # more years younger as the claimant's child. + "is_claimant_or_partner": {YEAR: True}, + "employment_income": { + YEAR: adult["employment_income"] + (earnings_bump if j == 0 else 0) + }, + "self_employment_income": {YEAR: adult["self_employment_income"]}, + "weekly_hours": {YEAR: adult["weekly_hours"]}, + "is_disabled_for_benefits": {YEAR: adult["disabled"]}, + "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}, + "is_claimant_or_partner": {YEAR: False}, + } + names.append(name) + benunits[f"b{i}"] = { + "members": names, + "income_support": {YEAR: income_support}, + "jsa_income": {YEAR: 0.0}, + "esa_income": {YEAR: 0.0}, + "in_specified_or_temporary_accommodation": { + YEAR: unit["accommodation"] if accommodation is None else accommodation + }, + } + households[f"h{i}"] = {"members": names} + return {"people": people, "benunits": benunits, "households": households} + + +VARIABLES = [ + "housing_benefit_applicable_income_disregard", + "housing_benefit_specified_or_temporary_accommodation_disregard", + "housing_benefit_net_earnings", + "housing_benefit_applicable_income", + "housing_benefit_applicable_income_childcare_element", + "meets_housing_benefit_additional_earnings_disregard_conditions", + "housing_benefit_pension_age_regulations_apply", +] + + +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 has_earner(unit): + # Para 18(1)(b): an employed or self-employed earner. + return any( + adult["employment_income"] > 0 or adult["self_employment_income"] != 0 + for adult in unit["adults"] + ) + + +def reference_para_18_weekly(unit): + """Para 18(1)-(2) from the statutory text, ignoring Income Support.""" + if not unit["accommodation"] or not has_earner(unit) or unit["pension_age"]: + return 0.0 + eldest = max(adult["age"] for adult in unit["adults"]) + if unit["shape"] in ("couple", "couple_with_children"): + if eldest >= 25: + return COUPLE_25_PLUS + if eldest >= 18: + return COUPLE_UNDER_25 + return COUPLE_BOTH_UNDER_18 + return SINGLE_25_PLUS if eldest >= 25 else SINGLE_UNDER_25 + + +def reference_disregard(unit, net, childcare, work_condition): + """Paras 4, 7, 10, 17 and 18 combined, from net earnings, the deductible + childcare charges and whether a para 17(2) work condition is met.""" + standard = min(BASE_WEEKLY[unit["shape"]] * WEEKS, net) + accommodation = min(reference_para_18_weekly(unit) * WEEKS, net - standard) + additional = ADDITIONAL_WEEKLY * WEEKS + covers = round(net, 2) >= round( + standard + accommodation + childcare + additional, 2 + ) + return standard + accommodation + (additional if work_condition and covers else 0) + + +@PROPERTY_SETTINGS +@given(st.lists(families(), min_size=1, max_size=40)) +def test_bounds(units): + values = calculate(units) + total = values["housing_benefit_applicable_income_disregard"] + para_18 = values["housing_benefit_specified_or_temporary_accommodation_disregard"] + net = values["housing_benefit_net_earnings"] + for i, unit in enumerate(units): + assert 0 <= para_18[i] <= COUPLE_25_PLUS * WEEKS + 0.01, unit + weekly = round(para_18[i] / WEEKS, 2) + assert weekly == 0 or weekly in STATUTORY_WEEKLY, (unit, weekly) + assert total[i] >= 0, unit + assert total[i] <= min(net[i], MAX_ANNUAL) + 0.01, unit + + +@PROPERTY_SETTINGS +@given(st.lists(families(accommodation=False), min_size=1, max_size=40)) +def test_no_para_18_amount_without_the_input(units): + values = calculate(units) + para_18 = values["housing_benefit_specified_or_temporary_accommodation_disregard"] + assert (para_18 == 0).all() + # The total is what the pre-para 18 formula gives: zeroing the amount + # changes nothing. + sim = Simulation(situation=situation(units)) + sim.set_input( + "housing_benefit_specified_or_temporary_accommodation_disregard", + YEAR, + np.zeros(len(units)), + ) + zeroed = np.asarray( + sim.calculate("housing_benefit_applicable_income_disregard", YEAR) + ) + total = values["housing_benefit_applicable_income_disregard"] + np.testing.assert_allclose(total, zeroed, atol=0.01) + + +@PROPERTY_SETTINGS +@given( + st.lists(families(accommodation=True), min_size=1, max_size=40), + st.floats(1, 5_000, allow_nan=False, allow_infinity=False), +) +def test_disregard_is_non_decreasing_in_earnings(units, bump): + key = "housing_benefit_applicable_income_disregard" + before = calculate(units)[key] + after = calculate(units, earnings_bump=bump)[key] + for i, unit in enumerate(units): + assert after[i] >= before[i] - 0.01, (unit, bump) + + +@PROPERTY_SETTINGS +@given(st.lists(families(), min_size=1, max_size=40)) +def test_the_input_never_lowers_the_disregard(units): + off = calculate(units, accommodation=False) + on = calculate(units, accommodation=True) + for i, unit in enumerate(units): + key = "housing_benefit_applicable_income_disregard" + assert on[key][i] >= off[key][i] - 0.01, unit + income = "housing_benefit_applicable_income" + assert on[income][i] <= off[income][i] + 0.01, unit + + +@PROPERTY_SETTINGS +@given(st.lists(families(), min_size=1, max_size=40)) +def test_differential_against_the_statutory_reference(units): + values = calculate(units) + for i, unit in enumerate(units): + assert ( + bool(values["housing_benefit_pension_age_regulations_apply"][i]) + == (unit["pension_age"]) + ), unit + expected_para_18 = reference_para_18_weekly(unit) * WEEKS + para_18 = values[ + "housing_benefit_specified_or_temporary_accommodation_disregard" + ] + assert abs(para_18[i] - expected_para_18) < 0.01, unit + expected = reference_disregard( + unit, + net=float(values["housing_benefit_net_earnings"][i]), + childcare=float( + values["housing_benefit_applicable_income_childcare_element"][i] + ), + work_condition=bool( + values[ + "meets_housing_benefit_additional_earnings_disregard_conditions" + ][i] + ), + ) + total = values["housing_benefit_applicable_income_disregard"][i] + assert abs(total - expected) < 0.02, (unit, total, expected) + + +@PROPERTY_SETTINGS +@given( + st.lists(families(pension_age=True, accommodation=True), min_size=1, max_size=40) +) +def test_pension_age_has_no_para_18_amount(units): + values = calculate(units) + para_18 = values["housing_benefit_specified_or_temporary_accommodation_disregard"] + assert (para_18 == 0).all() + + +@PROPERTY_SETTINGS +@given(st.lists(families(), min_size=1, max_size=40)) +def test_income_support_disregards_all_earnings(units): + values = calculate(units, income_support=1_000) + net = values["housing_benefit_net_earnings"] + total = values["housing_benefit_applicable_income_disregard"] + np.testing.assert_allclose(total, net, atol=0.01) + + +def test_amounts_by_year(): + for year in range(2015, 2036): + p = system.parameters( + year + ).gov.dwp.housing_benefit.means_test.income_disregard.specified_or_temporary_accommodation + amounts = ( + p.single.younger, + p.single.older, + p.lone_parent.younger, + p.lone_parent.older, + p.couple.minors, + p.couple.younger, + p.couple.older, + ) + if year <= 2026: + assert amounts == (0,) * 7, year + else: + assert amounts == ( + SINGLE_UNDER_25, + SINGLE_25_PLUS, + SINGLE_UNDER_25, + SINGLE_25_PLUS, + COUPLE_BOTH_UNDER_18, + COUPLE_UNDER_25, + COUPLE_25_PLUS, + ), year + assert (p.age_threshold.younger, p.age_threshold.older) == (18, 25), year + + +def test_parameter_files_start_on_5_october_2026(): + folder = ( + Path(__file__).parents[1] + / "parameters/gov/dwp/housing_benefit/means_test/income_disregard" + / "specified_or_temporary_accommodation" + ) + files = sorted(folder.rglob("*.yaml")) + assert len(files) == 9 + for path in files: + values = yaml.safe_load(path.read_text())["values"] + dates = sorted(str(date) for date in values) + assert "2026-10-05" in dates, path + for date in dates: + if date < "2026-10-05": + assert values[next(d for d in values if str(d) == date)] == 0, path 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 7a6c766d2..53979ccda 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 @@ -8,10 +8,14 @@ class housing_benefit_applicable_income_disregard(Variable): documentation = ( "Sums disregarded from the claimant's and partner's net earnings. " "A lone parent has £25 a week, a couple £10 and anyone else £5, " - "capped at net earnings. The additional earnings disregard of £17.10 " - "is added where a work condition is met and net earnings at least " - "equal the other disregards, the childcare charges deducted and " - "£17.10. A claimant who, or whose partner, is on Income Support, " + "capped at net earnings. From 5 October 2026 the working-age " + "Regulations add the specified or temporary accommodation disregard " + "(housing_benefit_specified_or_temporary_accommodation_disregard), " + "also 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 (including that one), the childcare " + "charges deducted and £17.10. A claimant who, or whose partner, is on " + "Income Support, " "income-based Jobseeker's Allowance or income-related Employment and " "Support Allowance has all earnings disregarded, at any age: the " "working-age Regulations then apply (SI 2006/213 reg 5(1)(b)). The " @@ -25,6 +29,8 @@ class housing_benefit_applicable_income_disregard(Variable): "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", + "https://www.legislation.gov.uk/uksi/2026/978/regulation/2", + "https://www.legislation.gov.uk/nisr/2026/157/regulation/2", ) def formula(benunit, period, parameters): @@ -37,6 +43,17 @@ def formula(benunit, period, parameters): default=p.single, ) standard = min_(weekly_amount * WEEKS_IN_YEAR, net_earnings) + # Working age Sch 4 para 18 (NI Sch 5 para 18), from 5 October 2026: + # specified or temporary accommodation. It applies alongside paras 3 + # to 10A, none of which excludes it, and takes the earnings they + # leave. + accommodation = min_( + benunit( + "housing_benefit_specified_or_temporary_accommodation_disregard", + period, + ), + net_earnings - standard, + ) # Working age Sch 4 para 17; pension age Sch 4 para 9. additional_amount = p.worker * WEEKS_IN_YEAR childcare = benunit( @@ -44,9 +61,12 @@ def formula(benunit, period, parameters): ) # "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. + # with the total fails at equality. From 5 October 2026 the total + # includes the para 18 amount (para 17(3)(a), "paragraphs 3 to 10A + # and 18"); it is zero under the pension-age para 9(3)(a). covers_additional = np.round(net_earnings.astype(float), 2) >= np.round( - (standard + childcare + additional_amount).astype(float), 2 + (standard + accommodation + childcare + additional_amount).astype(float), + 2, ) additional = where( benunit( @@ -64,4 +84,8 @@ def formula(benunit, period, parameters): on_income_related_benefit = ( add(benunit, period, ["income_support", "jsa_income", "esa_income"]) > 0 ) - return where(on_income_related_benefit, net_earnings, standard + additional) + return where( + on_income_related_benefit, + net_earnings, + standard + accommodation + additional, + ) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.py b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.py new file mode 100644 index 000000000..401d1c6f5 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.py @@ -0,0 +1,73 @@ +from policyengine_uk.model_api import * + + +class housing_benefit_specified_or_temporary_accommodation_disregard(Variable): + value_type = float + entity = BenUnit + label = "Housing Benefit specified or temporary accommodation earnings disregard" + documentation = ( + "The amount, from 5 October 2026, that the working-age Regulations " + "disregard from the net earnings of a claimant who lives in specified " + "or temporary accommodation where the claimant or partner is an " + "employed or self-employed earner: £61.41 a week for a single " + "claimant or lone parent under 25 and £77.73 at 25 or over; for a " + "couple, £97.33 where both are under 18, £61.53 where one is 18 or " + "over but both are under 25, and £119.70 where one is 25 or over. A " + "couple has one amount, taken from the claimant's earnings and then " + "from the partner's. This is the full amount; " + "housing_benefit_applicable_income_disregard adds it to the other " + "disregards and caps the total at net earnings. The pension-age " + "Regulations have no such disregard. The model reads each fiscal year " + "at 30 April, so it is zero for 2026-27 and in full from 2027-28." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/2026/753/regulation/2", + "https://www.legislation.gov.uk/uksi/2026/978/regulation/2", + "https://www.legislation.gov.uk/uksi/2006/213/schedule/4", + "https://www.legislation.gov.uk/nisr/2026/157/regulation/2", + "https://www.legislation.gov.uk/nisr/2006/405/schedule/5", + ) + + def formula(benunit, period, parameters): + p = parameters( + period + ).gov.dwp.housing_benefit.means_test.income_disregard.specified_or_temporary_accommodation + person = benunit.members + # Para 18(1)(b): the claimant or partner is an employed earner or a + # self-employed earner. Anyone with earnings is, including statutory + # sick or maternity pay from a continuing employment and a + # self-employment loss. The amount is capped at net earnings in + # housing_benefit_applicable_income_disregard, so a family without + # net earnings gets nothing either way. + earner = ( + (person("employment_income", period) > 0) + | (person("self_employment_income", period) != 0) + | (person("employment_benefits", period) > 0) + ) + has_earner = benunit.any(person("is_claimant_or_partner", period) & earner) + # Para 18(2): the claimant's age, or the elder member's for a couple. + eldest = benunit("eldest_claimant_or_partner_age", period) + older = eldest >= p.age_threshold.older + couple_amount = select( + [older, eldest >= p.age_threshold.younger], + [p.couple.older, p.couple.younger], + default=p.couple.minors, + ) + weekly_amount = select( + [benunit("is_lone_parent", period), benunit("is_couple", period)], + [ + where(older, p.lone_parent.older, p.lone_parent.younger), + couple_amount, + ], + default=where(older, p.single.older, p.single.younger), + ) + # Para 18(1)(a), and only in the working-age Regulations (SI 2006/213; + # NI SR 2006/405): SI 2006/214 and SR 2006/406 are not amended. + applies = ( + benunit("in_specified_or_temporary_accommodation", period) + & has_earner + & ~benunit("housing_benefit_pension_age_regulations_apply", period) + ) + return where(applies, weekly_amount * WEEKS_IN_YEAR, 0) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/in_specified_or_temporary_accommodation.py b/policyengine_uk/variables/gov/dwp/housing_benefit/in_specified_or_temporary_accommodation.py new file mode 100644 index 000000000..263e66ec9 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/in_specified_or_temporary_accommodation.py @@ -0,0 +1,34 @@ +from policyengine_uk.model_api import * + + +class in_specified_or_temporary_accommodation(Variable): + value_type = bool + entity = BenUnit + label = "In specified or temporary accommodation for Housing Benefit" + documentation = ( + "Whether this family's housing falls within the statutory specified " + "or temporary accommodation definitions for Housing Benefit. GB " + "Universal Credit (Transitional Provisions) Regulations 2014 reg " + "6A(2), with the definitions in reg 2, refers to Universal Credit " + "Regulations 2013 Schedule 1 paragraphs 3A(2)-(5) and 3B. The NI " + "equivalents are Transitional Provisions Regulations 2016 regs 2 " + "and 4A(2), and Universal Credit Regulations 2016 Schedule 1 " + "paragraphs 4(2)-(5) and 4A. This is a supplied status, not inferred " + "from tenure or a generic supported-housing label. The working-age " + "Housing Benefit earnings disregard for specified or temporary " + "accommodation (SI 2006/213 Sch 4 para 18; NI SR 2006/405 Sch 5 para " + "18), which refers to the same paragraphs, reads it. " + "It defaults to False when absent from household or dataset inputs." + ) + definition_period = YEAR + default_value = False + reference = ( + "https://www.legislation.gov.uk/uksi/2014/1230/regulation/6A/2", + "https://www.legislation.gov.uk/uksi/2014/1230/regulation/2", + "https://www.legislation.gov.uk/uksi/2013/376/schedule/1/paragraph/3A", + "https://www.legislation.gov.uk/uksi/2013/376/schedule/1/paragraph/3B", + "https://www.legislation.gov.uk/nisr/2016/226/regulation/4A/2", + "https://www.legislation.gov.uk/nisr/2016/226/regulation/2", + "https://www.legislation.gov.uk/nisr/2016/216/schedule/1/paragraph/4", + "https://www.legislation.gov.uk/nisr/2016/216/schedule/1/paragraph/4A", + ) From 5aaacd83f36720ab6f3692e543ec3eaa3704c274 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 17:53:36 -0400 Subject: [PATCH 2/6] Pin para 18(1)(b) in the property tests A mutant that made the earner condition always true survived the property suite (the YAML caught it). Draw families without earnings half the time, and add properties that no earner gets no para 18 amount and that a working-age earner in the accommodation gets exactly the statutory amount. Co-Authored-By: Claude Opus 5.5 --- ...fied_accommodation_disregard_properties.py | 53 ++++++++++++++++--- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py b/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py index dac050a39..370696e34 100644 --- a/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py +++ b/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py @@ -32,8 +32,10 @@ 5. Differential: the model agrees with an independent reference written from the statutory text, with the para 18(2) amounts hard-coded from the instrument rather than read from the parameters. -6. Pension age: families whose claimant and partner are all over State - Pension age get no para 18 amount. +6. Scope: families whose claimant and partner are all over State Pension + age, and families in the accommodation with no earner (para 18(1)(b)), + get no para 18 amount; a working-age family in the accommodation with an + earner gets exactly the statutory amount. 7. Para 12: on Income Support, every family has all its net earnings disregarded, with or without the input. 8. Dates: no para 18 amount in any year to 2026 (the model reads 2026-27 at @@ -98,28 +100,32 @@ @st.composite -def adults(draw, age): +def adults(draw, age, earns=True): return dict( age=draw(age), - employment_income=draw(EARNINGS), - self_employment_income=draw(SELF_EMPLOYMENT), + employment_income=draw(EARNINGS) if earns else 0.0, + self_employment_income=draw(SELF_EMPLOYMENT) if earns else 0.0, weekly_hours=draw(HOURS), disabled=draw(st.booleans()), ) @st.composite -def families(draw, pension_age=None, accommodation=None): +def families(draw, pension_age=None, accommodation=None, earners=None): shape = draw(st.sampled_from(sorted(SHAPES))) n_adults, n_children = SHAPES[shape] if pension_age is None: pension_age = draw(st.booleans()) age = PENSION_AGE if pension_age else WORKING_AGE + # Families without any earnings are drawn often, so that para 18(1)(b) + # is exercised on both sides. + if earners is None: + earners = draw(st.booleans()) return dict( shape=shape, pension_age=pension_age, accommodation=(draw(st.booleans()) if accommodation is None else accommodation), - adults=[draw(adults(age)) for _ in range(n_adults)], + adults=[draw(adults(age, earners)) 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 @@ -309,6 +315,39 @@ def test_differential_against_the_statutory_reference(units): assert abs(total - expected) < 0.02, (unit, total, expected) +@PROPERTY_SETTINGS +@given( + st.lists( + families(pension_age=False, accommodation=True, earners=False), + min_size=1, + max_size=40, + ) +) +def test_no_earner_has_no_para_18_amount(units): + # Para 18(1)(b): neither the claimant nor the partner is an earner. + values = calculate(units) + para_18 = values["housing_benefit_specified_or_temporary_accommodation_disregard"] + assert (para_18 == 0).all() + + +@PROPERTY_SETTINGS +@given( + st.lists( + families(pension_age=False, accommodation=True, earners=True), + min_size=1, + max_size=40, + ) +) +def test_working_age_earner_in_the_accommodation_has_the_statutory_amount(units): + values = calculate(units) + para_18 = values["housing_benefit_specified_or_temporary_accommodation_disregard"] + for i, unit in enumerate(units): + if not has_earner(unit): + continue # every drawn income was zero + assert para_18[i] > 0, unit + assert abs(para_18[i] - reference_para_18_weekly(unit) * WEEKS) < 0.01, unit + + @PROPERTY_SETTINGS @given( st.lists(families(pension_age=True, accommodation=True), min_size=1, max_size=40) From 88a3db3ec5fc11ef50f0b1164f5b1decf5cad718 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 18:52:26 -0400 Subject: [PATCH 3/6] Answer the para 18 review: edge cases, boundary examples, wording - YAML: a mixed-age couple under the pension-age Regulations (no para 18), statutory maternity pay as the only earnings, a self-employment loss (para 18(1)(b) met, nothing to disregard), and a pension-age claimant on Income Support (working-age Regulations, para 12 takes all earnings). - Hypothesis: pin the age boundaries (couples 17/17, 17/18, 24/25; singles and lone parents 24 and 25) as explicit examples of the differential, and say exactly what its reference shares with the model. - Parameters: note why fiscal_year_blend is not used. Co-Authored-By: Claude Opus 5.5 --- .../couple/minors.yaml | 4 +- .../couple/older.yaml | 4 +- .../couple/younger.yaml | 4 +- .../lone_parent/older.yaml | 4 +- .../lone_parent/younger.yaml | 4 +- .../single/older.yaml | 4 +- .../single/younger.yaml | 4 +- ..._or_temporary_accommodation_disregard.yaml | 84 +++++++++++++++++++ ...fied_accommodation_disregard_properties.py | 46 +++++++++- 9 files changed, 147 insertions(+), 11 deletions(-) diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/minors.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/minors.yaml index a2a6169f6..b44d66a34 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/minors.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/minors.yaml @@ -6,7 +6,9 @@ values: # Neither instrument uprates the sums. The model reads each fiscal year at # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards - # has the full amounts. + # has the full amounts. fiscal_year_blend is not used: half of an amount + # for the whole year is not the full amount for half the year, because + # the disregard is capped at net earnings and counts in the £17.10 test. 2006-03-06: 0 2026-10-05: 97.33 metadata: diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/older.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/older.yaml index 3c9bff884..93d44c2c4 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/older.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/older.yaml @@ -6,7 +6,9 @@ values: # Neither instrument uprates the sums. The model reads each fiscal year at # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards - # has the full amounts. + # has the full amounts. fiscal_year_blend is not used: half of an amount + # for the whole year is not the full amount for half the year, because + # the disregard is capped at net earnings and counts in the £17.10 test. 2006-03-06: 0 2026-10-05: 119.7 metadata: diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/younger.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/younger.yaml index b8c3ff090..0e156837f 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/younger.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/couple/younger.yaml @@ -6,7 +6,9 @@ values: # Neither instrument uprates the sums. The model reads each fiscal year at # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards - # has the full amounts. + # has the full amounts. fiscal_year_blend is not used: half of an amount + # for the whole year is not the full amount for half the year, because + # the disregard is capped at net earnings and counts in the £17.10 test. 2006-03-06: 0 2026-10-05: 61.53 metadata: diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/older.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/older.yaml index 8e1d8ec6c..a038a6223 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/older.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/older.yaml @@ -6,7 +6,9 @@ values: # Neither instrument uprates the sums. The model reads each fiscal year at # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards - # has the full amounts. + # has the full amounts. fiscal_year_blend is not used: half of an amount + # for the whole year is not the full amount for half the year, because + # the disregard is capped at net earnings and counts in the £17.10 test. 2006-03-06: 0 2026-10-05: 77.73 metadata: diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/younger.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/younger.yaml index ccf90edf3..4875f0319 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/younger.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/lone_parent/younger.yaml @@ -6,7 +6,9 @@ values: # Neither instrument uprates the sums. The model reads each fiscal year at # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards - # has the full amounts. + # has the full amounts. fiscal_year_blend is not used: half of an amount + # for the whole year is not the full amount for half the year, because + # the disregard is capped at net earnings and counts in the £17.10 test. 2006-03-06: 0 2026-10-05: 61.41 metadata: diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/older.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/older.yaml index f40eaf46c..63bc144f2 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/older.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/older.yaml @@ -6,7 +6,9 @@ values: # Neither instrument uprates the sums. The model reads each fiscal year at # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards - # has the full amounts. + # has the full amounts. fiscal_year_blend is not used: half of an amount + # for the whole year is not the full amount for half the year, because + # the disregard is capped at net earnings and counts in the £17.10 test. 2006-03-06: 0 2026-10-05: 77.73 metadata: diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/younger.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/younger.yaml index c04bfaed0..10cfa31c0 100644 --- a/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/younger.yaml +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/means_test/income_disregard/specified_or_temporary_accommodation/single/younger.yaml @@ -6,7 +6,9 @@ values: # Neither instrument uprates the sums. The model reads each fiscal year at # 30 April, so 2026 (2026-27) has no paragraph 18 disregard although it # applied from 5 October 2026 (183 of the year's 365 days); 2027 onwards - # has the full amounts. + # has the full amounts. fiscal_year_blend is not used: half of an amount + # for the whole year is not the full amount for half the year, because + # the disregard is capped at net earnings and counts in the £17.10 test. 2006-03-06: 0 2026-10-05: 61.41 metadata: diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml index 4a6cb74cd..b26c44135 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_specified_or_temporary_accommodation_disregard.yaml @@ -480,6 +480,90 @@ output: housing_benefit_applicable_income_disregard: 1_040 # Sch 4 para 12 +- name: Mixed-age couple under the pension-age Regulations has no para 18 amount + period: 2027 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + is_claimant_or_partner: true + state_pension_reported: 15_000 + partner: + age: 55 + is_claimant_or_partner: true + employment_income: 10_400 # £200 a week + weekly_hours: 10 + benunits: + benunit: + members: [claimant, partner] + in_specified_or_temporary_accommodation: true + output: + # The claimant has reached the qualifying age and neither is on + # Universal Credit or an income-related benefit, so SI 2006/214 applies + # (reg 5), which has no para 18. + is_uc_entitled: false + housing_benefit_pension_age_regulations_apply: true + housing_benefit_specified_or_temporary_accommodation_disregard: 0 + housing_benefit_applicable_income_disregard: 520 # SI 2006/214 Sch 4 para 7(b): £10 × 52 + +- name: Statutory maternity pay alone makes the claimant an employed earner + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + statutory_maternity_pay: 5_200 # £100 a week, earnings under reg 35(1)(i) + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_net_earnings: 5_200 + housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 + housing_benefit_applicable_income_disregard: 4_301.96 # (£5 + £77.73) × 52 + +- name: A self-employment loss is still self-employment, but leaves no earnings to disregard + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 30 + self_employment_income: -2_000 + benunits: + benunit: + members: [person] + in_specified_or_temporary_accommodation: true + output: + housing_benefit_net_earnings: 0 + # Para 18(1)(b) is met, so the full amount applies... + housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 + # ...but it comes off net earnings, which are nil. + housing_benefit_applicable_income_disregard: 0 + +- name: Pension-age claimant on Income Support falls under the working-age Regulations, where para 12 takes all earnings + period: 2027 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + employment_income: 1_040 # £20 a week + benunits: + benunit: + members: [person] + income_support: 3_000 + in_specified_or_temporary_accommodation: true + output: + # SI 2006/213 reg 5(1)(b): the working-age Regulations apply at any age + # to a claimant on Income Support, so para 18 is in scope... + housing_benefit_pension_age_regulations_apply: false + housing_benefit_specified_or_temporary_accommodation_disregard: 4_041.96 + # ...but para 12 disregards all earnings anyway. + housing_benefit_applicable_income_disregard: 1_040 + # The £17.10 additional earnings disregard (para 17) and its earnings test, # which from 5 October 2026 counts the para 18 amount (para 17(3)(a)). diff --git a/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py b/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py index 370696e34..175dd89a9 100644 --- a/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py +++ b/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py @@ -29,9 +29,14 @@ never raises applicable income. This holds because every para 18 amount exceeds the £17.10 it can take away through para 17(3)(a); it is a property of the amounts, not a rule of law. -5. Differential: the model agrees with an independent reference written from - the statutory text, with the para 18(2) amounts hard-coded from the - instrument rather than read from the parameters. +5. Differential: the model agrees with a reference written from the + statutory text. The reference is independent for the para 18 bands and + gates (amounts hard-coded from the instrument, ages, family type, earner + and pension-age status from the generated inputs) and for how paras 4, 7, + 10, 17 and 18 combine. It takes net earnings, deductible childcare and + the para 17(2) work condition from the model, which other tests cover. + Explicit examples pin the age boundaries (couples aged 17/17, 17/18 and + 24/25; single claimants and lone parents aged 24 and 25). 6. Scope: families whose claimant and partner are all over State Pension age, and families in the accommodation with no earner (para 18(1)(b)), get no para 18 amount; a working-age family in the accommodation with an @@ -48,7 +53,7 @@ import numpy as np import yaml -from hypothesis import HealthCheck, given, settings +from hypothesis import HealthCheck, example, given, settings from hypothesis import strategies as st from policyengine_uk import Simulation @@ -285,8 +290,41 @@ def test_the_input_never_lowers_the_disregard(units): assert on[income][i] <= off[income][i] + 0.01, unit +def boundary_family(shape, ages, children=()): + return dict( + shape=shape, + pension_age=False, + accommodation=True, + adults=[ + dict( + age=age, + employment_income=10_400.0 if j == 0 else 0.0, + self_employment_income=0.0, + weekly_hours=30.0, + disabled=False, + ) + for j, age in enumerate(ages) + ], + children=list(children), + childcare=0.0, + ) + + +BOUNDARY_FAMILIES = [ + boundary_family("couple", (17, 17)), + boundary_family("couple", (17, 18)), + boundary_family("couple", (24, 25)), + boundary_family("couple_with_children", (17, 17), (1,)), + boundary_family("single", (24,)), + boundary_family("single", (25,)), + boundary_family("lone_parent", (24,), (3, 5)), + boundary_family("lone_parent", (25,), (3, 5)), +] + + @PROPERTY_SETTINGS @given(st.lists(families(), min_size=1, max_size=40)) +@example(units=BOUNDARY_FAMILIES) def test_differential_against_the_statutory_reference(units): values = calculate(units) for i, unit in enumerate(units): From 0857ba5ceae6039192e7ca11e548c8192f1bf885 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 4 Oct 2026 07:16:15 -0400 Subject: [PATCH 4/6] Document the para 10A and universal credit limits of the HB disregard The permitted work disregard (Sch 4 para 10A) replaces paras 3 to 10 but not para 18, and the universal credit limb of para 12 is left out because the model does not pay Housing Benefit to a family on Universal Credit. Neither is new; the review asked for both to be stated. Co-Authored-By: Claude Opus 5.5 --- .../housing_benefit_applicable_income_disregard.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 53979ccda..ba2414d9c 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 @@ -20,7 +20,10 @@ class housing_benefit_applicable_income_disregard(Variable): "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." + "occupations are not modelled, nor is the working-age permitted work " + "disregard (Sch 4 para 10A), which replaces paras 3 to 10 but not " + "para 18. Nor is the universal credit limb of para 12: the model does " + "not pay Housing Benefit to a family on Universal Credit." ) definition_period = YEAR unit = GBP From 9bf692a5ced7b22a9118f2ec533b365b7b00152e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 4 Oct 2026 07:42:37 -0400 Subject: [PATCH 5/6] Pin the para 17(3)(a) window in the para 18 differential MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drawing families without earnings half the time (5aaacd83) left the derandomized property suite without a family whose net earnings cover the £17.10 test without para 18 but not with it, so it no longer killed a mutant that drops para 18 from para 17(3)(a); the YAML still did. Add two such families as explicit examples (single, 30 hours, £5,148; lone parent, 16 hours, £6,000) and state the parameter-date invariant exactly. Co-Authored-By: Claude Opus 5.5 --- ...fied_accommodation_disregard_properties.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py b/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py index 175dd89a9..c65119658 100644 --- a/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py +++ b/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py @@ -45,8 +45,9 @@ disregarded, with or without the input. 8. Dates: no para 18 amount in any year to 2026 (the model reads 2026-27 at 30 April 2026, before 5 October), the same amounts in every year from - 2027 to 2035 (nothing uprates them), and the parameter files start on 5 - October 2026. + 2027 to 2035 (nothing uprates them). Each amount file's first non-zero + value takes effect on 5 October 2026, with zero before; the two age + thresholds start on that date. """ from pathlib import Path @@ -290,7 +291,7 @@ def test_the_input_never_lowers_the_disregard(units): assert on[income][i] <= off[income][i] + 0.01, unit -def boundary_family(shape, ages, children=()): +def boundary_family(shape, ages, children=(), earnings=10_400.0, hours=30.0): return dict( shape=shape, pension_age=False, @@ -298,9 +299,9 @@ def boundary_family(shape, ages, children=()): adults=[ dict( age=age, - employment_income=10_400.0 if j == 0 else 0.0, + employment_income=earnings if j == 0 else 0.0, self_employment_income=0.0, - weekly_hours=30.0, + weekly_hours=hours, disabled=False, ) for j, age in enumerate(ages) @@ -319,6 +320,14 @@ def boundary_family(shape, ages, children=()): boundary_family("single", (25,)), boundary_family("lone_parent", (24,), (3, 5)), boundary_family("lone_parent", (25,), (3, 5)), + # Inside the para 17(3)(a) window: the work condition is met and net + # earnings cover the £17.10 test without para 18 but not with it. + # Single, 30 hours: £5,148 is between (£5 + £17.10) x 52 = £1,149.20 and + # (£5 + £77.73 + £17.10) x 52 = £5,191.16. + boundary_family("single", (30,), earnings=5_148.0), + # Lone parent, 16 hours: £6,000 is between £2,189.20 and + # (£25 + £77.73 + £17.10) x 52 = £6,231.16. + boundary_family("lone_parent", (30,), (5,), earnings=6_000.0, hours=16.0), ] From a3ee31aa68fd223289eba13ddc1e79f31acada14 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 4 Oct 2026 07:50:37 -0400 Subject: [PATCH 6/6] Draw money in whole pennies in the para 18 property tests The model stores money as float32, so a tiny positive float drawn by Hypothesis (say 1e-100) would round to zero there and stop counting as earnings, while the reference's earner test still counted it. The derandomized run never drew one, but a larger run could fail spuriously. Co-Authored-By: Claude Opus 5.5 --- ...ified_accommodation_disregard_properties.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py b/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py index c65119658..a07f36cf5 100644 --- a/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py +++ b/policyengine_uk/tests/test_housing_benefit_specified_accommodation_disregard_properties.py @@ -96,13 +96,21 @@ HOURS = st.one_of( st.sampled_from([0.0, 15.0, 16.0, 29.0, 30.0]), st.floats(0, 50, allow_nan=False) ) + + +def pounds(low, high): + # Whole pennies: the model stores money as float32, where a tiny positive + # float would round to zero and so stop counting as earnings. + return st.integers(round(low * 100), round(high * 100)).map(lambda p: p / 100) + + EARNINGS = st.one_of( st.just(0.0), - st.floats(0, 3_000, allow_nan=False), - st.floats(0, 12_000, allow_nan=False), - st.floats(0, 60_000, allow_nan=False), + pounds(0, 3_000), + pounds(0, 12_000), + pounds(0, 60_000), ) -SELF_EMPLOYMENT = st.one_of(st.just(0.0), st.floats(0, 12_000, allow_nan=False)) +SELF_EMPLOYMENT = st.one_of(st.just(0.0), pounds(0, 12_000)) @st.composite @@ -134,7 +142,7 @@ def families(draw, pension_age=None, accommodation=None, earners=None): adults=[draw(adults(age, earners)) 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 + draw(st.one_of(st.just(0.0), pounds(0, 10_000))) if n_children else 0.0 ), )