From 1d1ed5b2b56e81ecf792e01e9388d1d4cc5f7922 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 08:28:19 -0400 Subject: [PATCH 1/7] Split pension-age Housing Benefit allowances by when State Pension age was attained MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 1 April 2021, SI 2006/214 Sch 3 para 1 (as amended by SI 2021/188; NI: SR 2006/406 Sch 4 para 1) gives the lower pension-age personal allowance to a single claimant or lone parent who attained pensionable age on or after that date, and to a couple both of whom did: £238.00 and £363.25 a week in 2026-27, against £256.00 and £383.35 for earlier cohorts. PolicyEngine applied the higher rate to everyone. - state_pension_age_date: the day each person attains State Pension age, as a YYYYMMDD number, from the Pensions Act 1995 timetable. state_pension_age now derives from it (same results; the timetable logic lives in one place). - housing_benefit_attained_pension_age_before_cutoff and the cutoff and lower-rate parameters (2021-22 to 2026-27 from SI 2021/188 and the Up-rating Orders 2022-2026). - England's pensioner Council Tax Reduction has the same split (SI 2012/2885 Sch 2 para 1, amended by SI 2021/29); Scotland and Wales keep one rate. - YAML boundary tests, a differential test of the attainment day against the statute for every birth date, and property tests of the cohort rule. Co-Authored-By: Claude Opus 5.5 --- .../hb-pension-age-allowance-cohorts.fixed.md | 1 + .../programs/gov/dwp/housing-benefit.ipynb | 16 +- .../allowances/couple/aged_from_cutoff.yaml | 30 ++ .../lone_parent/aged_from_cutoff.yaml | 30 ++ .../allowances/pension_age_cutoff.yaml | 13 + .../allowances/single/aged_from_cutoff.yaml | 30 ++ ...enefit_pension_age_personal_allowance.yaml | 280 ++++++++++++++++++ .../gov/dwp/state_pension_age_date.yaml | 104 +++++++ ...uction_pension_age_personal_allowance.yaml | 133 +++++++++ ...est_housing_benefit_pension_age_cohorts.py | 187 ++++++++++++ .../tests/test_state_pension_age.py | 12 + .../housing_benefit_applicable_amount.py | 25 +- ...efit_attained_pension_age_before_cutoff.py | 39 +++ .../variables/gov/dwp/state_pension_age.py | 30 +- .../gov/dwp/state_pension_age_date.py | 51 ++++ ...council_tax_reduction_applicable_amount.py | 30 +- 16 files changed, 972 insertions(+), 39 deletions(-) create mode 100644 changelog.d/hb-pension-age-allowance-cohorts.fixed.md create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged_from_cutoff.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged_from_cutoff.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/pension_age_cutoff.yaml create mode 100644 policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged_from_cutoff.yaml create mode 100644 policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/dwp/state_pension_age_date.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml create mode 100644 policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py create mode 100644 policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_attained_pension_age_before_cutoff.py create mode 100644 policyengine_uk/variables/gov/dwp/state_pension_age_date.py diff --git a/changelog.d/hb-pension-age-allowance-cohorts.fixed.md b/changelog.d/hb-pension-age-allowance-cohorts.fixed.md new file mode 100644 index 0000000000..d74617015d --- /dev/null +++ b/changelog.d/hb-pension-age-allowance-cohorts.fixed.md @@ -0,0 +1 @@ +Give pension-age Housing Benefit claimants who attained State Pension age on or after 1 April 2021 (and couples who both did) the lower personal allowance in SI 2006/214 Sch 3 para 1(1)(c) and (2)(c), £238.00 and £363.25 a week in 2026-27, instead of the £256.00 and £383.35 kept by earlier cohorts. England's pensioner Council Tax Reduction applies the same split (SI 2012/2885 Sch 2 para 1); Scotland and Wales keep one rate. Adds `state_pension_age_date`, the day each person attains State Pension age. diff --git a/docs/book/programs/gov/dwp/housing-benefit.ipynb b/docs/book/programs/gov/dwp/housing-benefit.ipynb index 4f928b173d..4c4606ea70 100644 --- a/docs/book/programs/gov/dwp/housing-benefit.ipynb +++ b/docs/book/programs/gov/dwp/housing-benefit.ipynb @@ -53,7 +53,11 @@ "source": [ "## Personal allowances\n", "\n", - "The applicable amount uses age- and household-composition bands. The chart below shows how the weekly allowances have evolved." + "The applicable amount uses age- and household-composition bands. The chart below shows how the weekly allowances have evolved.\n", + "\n", + "Since 1 April 2021 the pension-age allowances depend on when members of the benunit attained State Pension age ([SI 2006/214 Sch. 3 para. 1](https://www.legislation.gov.uk/uksi/2006/214/schedule/3), as amended by [SI 2021/188](https://www.legislation.gov.uk/uksi/2021/188/regulation/2/made); Northern Ireland: [SR 2006/406 Sch. 4 para. 1](https://www.legislation.gov.uk/nisr/2006/406/schedule/4)). A single claimant or lone parent who attained it before 1 April 2021, or a couple in which either member did, keeps the higher allowance (`aged`, £256.00 single and £383.35 couple a week in 2026-27). Where every member attained it on or after that date, the lower allowance applies (`aged_from_cutoff`, £238.00 and £363.25). `housing_benefit_attained_pension_age_before_cutoff` compares each member's `state_pension_age_date`, the day set by their date of birth under the Pensions Act 1995 timetable, with `pension_age_cutoff`. The regulations do not settle a couple in which one member has not yet reached State Pension age and the other reached it after the cutoff; PolicyEngine gives it the lower allowance, following the explanatory note to SI 2021/188.\n", + "\n", + "Council Tax Reduction reuses these allowances. England's pensioner scheme has the same split and amounts ([SI 2012/2885 Sch. 2 para. 1](https://www.legislation.gov.uk/uksi/2012/2885/schedule/2), as amended by [SI 2021/29](https://www.legislation.gov.uk/uksi/2021/29/contents/made)). The Scottish ([SSI 2012/319 Sch. 1 para. 2](https://www.legislation.gov.uk/ssi/2012/319/schedule/1)) and Welsh ([SI 2013/3029 Sch. 2 para. 1](https://www.legislation.gov.uk/wsi/2013/3029/schedule/2)) schemes keep one pension-age rate, the higher one." ] }, { @@ -81,16 +85,18 @@ "params = {\n", " \"Single (younger)\": allowances.single.younger,\n", " \"Single (older)\": allowances.single.older,\n", - " \"Single (aged)\": allowances.single.aged,\n", + " \"Single (pension age)\": allowances.single.aged,\n", + " \"Single (pension age, reached from April 2021)\": allowances.single.aged_from_cutoff,\n", " \"Lone parent (younger)\": allowances.lone_parent.younger,\n", " \"Lone parent (older)\": allowances.lone_parent.older,\n", - " \"Lone parent (aged)\": allowances.lone_parent.aged,\n", + " \"Lone parent (pension age)\": allowances.lone_parent.aged,\n", " \"Couple (younger)\": allowances.couple.younger,\n", " \"Couple (older)\": allowances.couple.older,\n", - " \"Couple (aged)\": allowances.couple.aged,\n", + " \"Couple (pension age)\": allowances.couple.aged,\n", + " \"Couple (pension age, both reached from April 2021)\": allowances.couple.aged_from_cutoff,\n", "}\n", "\n", - "dates = [f\"{y}-04-01\" for y in range(2016, 2026)]\n", + "dates = [f\"{y}-04-01\" for y in range(2016, 2027)]\n", "rows = [\n", " {\"date\": d, \"composition\": name, \"weekly allowance\": p(d)}\n", " for d in dates\n", diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged_from_cutoff.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged_from_cutoff.yaml new file mode 100644 index 0000000000..9a36d00ec6 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/couple/aged_from_cutoff.yaml @@ -0,0 +1,30 @@ +description: Personal allowance for Housing Benefit for a couple where both members attained State Pension age on or after the pension-age cutoff (1 April 2021). +values: + 2021-04-01: 270.3 + 2022-04-01: 278.7 + 2023-04-01: 306.85 + 2024-04-01: 332.95 + 2025-04-01: 346.6 + 2026-04-01: 363.25 +metadata: + period: week + unit: currency-GBP + label: Housing Benefit couple personal allowance, State Pension age reached from April 2021 + uprating: gov.benefit_uprating_cpi + reference: + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) (Amendment) Regulations 2021 (SI 2021/188), regulation 2(3), inserting paragraph 1(2)(c) of Schedule 3 to SI 2006/214 from 1 April 2021 + href: https://www.legislation.gov.uk/uksi/2021/188/regulation/2/made + - title: SI 2006/214, Schedule 3, paragraph 1(2)(c), as in force on 2022-04-11 (set by the Social Security Benefits Up-rating Order 2022, SI 2022/292) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2022-04-11 + - title: SI 2006/214, Schedule 3, paragraph 1(2)(c), as in force on 2023-04-10 (set by the Social Security Benefits Up-rating Order 2023, SI 2023/316) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2023-04-10 + - title: SI 2006/214, Schedule 3, paragraph 1(2)(c), as in force on 2024-04-08 (set by the Social Security Benefits Up-rating Order 2024, SI 2024/242) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2024-04-08 + - title: SI 2006/214, Schedule 3, paragraph 1(2)(c), as in force on 2025-04-07 (set by the Social Security Benefits Up-rating Order 2025, SI 2025/295) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2025-04-07 + - title: SI 2006/214, Schedule 3, paragraph 1(2)(c), as substituted from April 2026 by the Social Security Benefits Up-rating Order 2026 (SI 2026/148), Schedule 6 + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006 (SR 2006/406), Schedule 4, paragraph 1 (the same amounts) + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/4 + - title: Benefit and pension rates 2026 to 2027 (2025-26 and 2026-27 rates) + href: https://www.gov.uk/government/publications/benefit-and-pension-rates-2026-to-2027/proposed-benefit-and-pension-rates-2026-to-2027 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged_from_cutoff.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged_from_cutoff.yaml new file mode 100644 index 0000000000..17e5e79a24 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/lone_parent/aged_from_cutoff.yaml @@ -0,0 +1,30 @@ +description: Personal allowance for Housing Benefit for a lone parent who attained State Pension age on or after the pension-age cutoff (1 April 2021). +values: + 2021-04-01: 177.1 + 2022-04-01: 182.6 + 2023-04-01: 201.05 + 2024-04-01: 218.15 + 2025-04-01: 227.1 + 2026-04-01: 238 +metadata: + period: week + unit: currency-GBP + label: Housing Benefit lone parent personal allowance, State Pension age reached from April 2021 + uprating: gov.benefit_uprating_cpi + reference: + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) (Amendment) Regulations 2021 (SI 2021/188), regulation 2(3), inserting paragraph 1(1)(c) of Schedule 3 to SI 2006/214 from 1 April 2021 + href: https://www.legislation.gov.uk/uksi/2021/188/regulation/2/made + - title: SI 2006/214, Schedule 3, paragraph 1(1)(c), as in force on 2022-04-11 (set by the Social Security Benefits Up-rating Order 2022, SI 2022/292) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2022-04-11 + - title: SI 2006/214, Schedule 3, paragraph 1(1)(c), as in force on 2023-04-10 (set by the Social Security Benefits Up-rating Order 2023, SI 2023/316) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2023-04-10 + - title: SI 2006/214, Schedule 3, paragraph 1(1)(c), as in force on 2024-04-08 (set by the Social Security Benefits Up-rating Order 2024, SI 2024/242) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2024-04-08 + - title: SI 2006/214, Schedule 3, paragraph 1(1)(c), as in force on 2025-04-07 (set by the Social Security Benefits Up-rating Order 2025, SI 2025/295) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2025-04-07 + - title: SI 2006/214, Schedule 3, paragraph 1(1)(c), as substituted from April 2026 by the Social Security Benefits Up-rating Order 2026 (SI 2026/148), Schedule 6 + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006 (SR 2006/406), Schedule 4, paragraph 1 (the same amounts) + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/4 + - title: Benefit and pension rates 2026 to 2027 (2025-26 and 2026-27 rates) + href: https://www.gov.uk/government/publications/benefit-and-pension-rates-2026-to-2027/proposed-benefit-and-pension-rates-2026-to-2027 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/pension_age_cutoff.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/pension_age_cutoff.yaml new file mode 100644 index 0000000000..990ae7995c --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/pension_age_cutoff.yaml @@ -0,0 +1,13 @@ +description: Since April 2021, pension-age Housing Benefit personal allowances depend on when members of the benefit unit attained State Pension age. The higher allowances apply if any member attained it before this date, written as a YYYYMMDD number; the lower allowances apply if every member attained it on or after this date. +values: + 2021-04-01: 20210401 +metadata: + unit: date + label: Housing Benefit pension-age personal allowance cutoff + reference: + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) (Amendment) Regulations 2021 (SI 2021/188), regulation 2 + href: https://www.legislation.gov.uk/uksi/2021/188/regulation/2/made + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations 2006 (SI 2006/214), Schedule 3, paragraph 1 + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006 (SR 2006/406), Schedule 4, paragraph 1 + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/4 diff --git a/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged_from_cutoff.yaml b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged_from_cutoff.yaml new file mode 100644 index 0000000000..5c446ef80a --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/housing_benefit/allowances/single/aged_from_cutoff.yaml @@ -0,0 +1,30 @@ +description: Personal allowance for Housing Benefit for a single claimant who attained State Pension age on or after the pension-age cutoff (1 April 2021). +values: + 2021-04-01: 177.1 + 2022-04-01: 182.6 + 2023-04-01: 201.05 + 2024-04-01: 218.15 + 2025-04-01: 227.1 + 2026-04-01: 238 +metadata: + period: week + unit: currency-GBP + label: Housing Benefit single personal allowance, State Pension age reached from April 2021 + uprating: gov.benefit_uprating_cpi + reference: + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) (Amendment) Regulations 2021 (SI 2021/188), regulation 2(3), inserting paragraph 1(1)(c) of Schedule 3 to SI 2006/214 from 1 April 2021 + href: https://www.legislation.gov.uk/uksi/2021/188/regulation/2/made + - title: SI 2006/214, Schedule 3, paragraph 1(1)(c), as in force on 2022-04-11 (set by the Social Security Benefits Up-rating Order 2022, SI 2022/292) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2022-04-11 + - title: SI 2006/214, Schedule 3, paragraph 1(1)(c), as in force on 2023-04-10 (set by the Social Security Benefits Up-rating Order 2023, SI 2023/316) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2023-04-10 + - title: SI 2006/214, Schedule 3, paragraph 1(1)(c), as in force on 2024-04-08 (set by the Social Security Benefits Up-rating Order 2024, SI 2024/242) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2024-04-08 + - title: SI 2006/214, Schedule 3, paragraph 1(1)(c), as in force on 2025-04-07 (set by the Social Security Benefits Up-rating Order 2025, SI 2025/295) + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3/2025-04-07 + - title: SI 2006/214, Schedule 3, paragraph 1(1)(c), as substituted from April 2026 by the Social Security Benefits Up-rating Order 2026 (SI 2026/148), Schedule 6 + href: https://www.legislation.gov.uk/uksi/2006/214/schedule/3 + - title: The Housing Benefit (Persons who have attained the qualifying age for state pension credit) Regulations (Northern Ireland) 2006 (SR 2006/406), Schedule 4, paragraph 1 (the same amounts) + href: https://www.legislation.gov.uk/nisr/2006/406/schedule/4 + - title: Benefit and pension rates 2026 to 2027 (2025-26 and 2026-27 rates) + href: https://www.gov.uk/government/publications/benefit-and-pension-rates-2026-to-2027/proposed-benefit-and-pension-rates-2026-to-2027 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml new file mode 100644 index 0000000000..a6244d0f93 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml @@ -0,0 +1,280 @@ +# Pension-age Housing Benefit personal allowances by when State Pension age was +# attained: SI 2006/214 Sch 3 para 1 (NI: SR 2006/406 Sch 4 para 1), as amended +# by SI 2021/188 from 1 April 2021. Higher rates (1)(b)/(2)(b) where a member +# attained pensionable age before 1 April 2021; lower rates (1)(c)/(2)(c) where +# every member attained it on or after. Amounts are the Up-rating Order rates. +# Age and months_since_last_birthday place each date of birth at 6 October. + +- name: Single woman who attained State Pension age on 31 March 2021 keeps the higher rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 71 + months_since_last_birthday: 6.19355 + is_male: false + benunits: + benunit: + members: [claimant] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: true + housing_benefit_applicable_amount: 256.00 * 52 # para 1(1)(b), claimant attained 31 March 2021 + +- name: Single man who attained State Pension age on 1 April 2021 gets the lower rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 71 + months_since_last_birthday: 6.1613 + is_male: true + benunits: + benunit: + members: [claimant] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: false + housing_benefit_applicable_amount: 238.00 * 52 # para 1(1)(c), claimant attained 1 April 2021 + +- name: Single person aged 80 gets the higher rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 80 + months_since_last_birthday: 4.1613 + is_male: false + benunits: + benunit: + members: [claimant] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: true + housing_benefit_applicable_amount: 256.00 * 52 # para 1(1)(b), claimant attained 1 June 2006 + +- name: Single person aged 70 gets the lower rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + months_since_last_birthday: 6.0 + is_male: false + benunits: + benunit: + members: [claimant] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: false + housing_benefit_applicable_amount: 238.00 * 52 # para 1(1)(c), claimant attained 6 April 2022 + +- name: In 2021-22, a single person who attained State Pension age on 31 March 2021 gets the higher rate + period: 2021 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 66 + months_since_last_birthday: 6.19355 + is_male: false + benunits: + benunit: + members: [claimant] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: true + housing_benefit_applicable_amount: 191.15 * 52 # para 1(1)(b), claimant attained 31 March 2021 + +- name: In 2021-22, a single person who attained State Pension age on 1 April 2021 gets the lower rate + period: 2021 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 66 + months_since_last_birthday: 6.1613 + is_male: false + benunits: + benunit: + members: [claimant] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: false + housing_benefit_applicable_amount: 177.10 * 52 # para 1(1)(c), claimant attained 1 April 2021 + +- name: Couple where one member attained State Pension age before April 2021 gets the higher rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 80 + months_since_last_birthday: 4.1613 + is_male: true + younger: + age: 70 + months_since_last_birthday: 6.0 + is_male: false + benunits: + benunit: + members: [older, younger] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: true + housing_benefit_applicable_amount: 383.35 * 52 # para 1(2)(b), older attained 1 June 2011, younger attained 6 April 2022 + +- name: Couple where both attained State Pension age after April 2021 gets the lower rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + months_since_last_birthday: 6.0 + is_male: true + younger: + age: 68 + months_since_last_birthday: 9.1613 + is_male: false + benunits: + benunit: + members: [older, younger] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: false + housing_benefit_applicable_amount: 363.25 * 52 # para 1(2)(c), older attained 6 April 2022, younger attained 1 January 2024 + +- name: Couple on either side of the cutoff by one day gets the higher rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 71 + months_since_last_birthday: 6.19355 + is_male: true + younger: + age: 71 + months_since_last_birthday: 6.1613 + is_male: false + benunits: + benunit: + members: [older, younger] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: true + housing_benefit_applicable_amount: 383.35 * 52 # para 1(2)(b), older attained 31 March 2021, younger attained 1 April 2021 + +- name: Mixed-age couple whose older member attained State Pension age after April 2021 gets the lower rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + months_since_last_birthday: 6.0 + is_male: true + younger: + age: 60 + months_since_last_birthday: 9.1613 + is_male: false + benunits: + benunit: + members: [older, younger] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: false + housing_benefit_applicable_amount: 363.25 * 52 # para 1(2)(c), older attained 6 April 2022, younger attained 1 January 2033 + +- name: Mixed-age couple whose older member attained State Pension age before April 2021 gets the higher rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 80 + months_since_last_birthday: 4.1613 + is_male: true + younger: + age: 60 + months_since_last_birthday: 9.1613 + is_male: false + benunits: + benunit: + members: [older, younger] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + housing_benefit_attained_pension_age_before_cutoff: true + housing_benefit_applicable_amount: 383.35 * 52 # para 1(2)(b), older attained 1 June 2011, younger attained 1 January 2033 + +- name: Pension-age lone parent who attained State Pension age after April 2021 gets the lower rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + months_since_last_birthday: 6.0 + is_male: false + grandchild: + age: 12 + benunits: + benunit: + members: [claimant, grandchild] + housing_benefit_eligible: true + benefits_premiums: 0 + output: + is_lone_parent: true + housing_benefit_attained_pension_age_before_cutoff: false + housing_benefit_applicable_amount: 238.00 * 52 # para 1(1)(c) + +- name: Before April 2021 every pension-age unit counts as attaining State Pension age before the cutoff + period: 2020 + input: + people: + claimant: + age: 66 + months_since_last_birthday: 0.0 + is_male: false + benunits: + benunit: + members: [claimant] + output: + housing_benefit_attained_pension_age_before_cutoff: true + +- name: Private renter aged 70 with a £30,000 State Pension is assessed at the lower rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + state_pension_reported: 30_000 + benunits: + benunit: + members: [person] + households: + household: + members: [person] + rent: 9_000 + tenure_type: RENT_PRIVATELY + output: + housing_benefit_attained_pension_age_before_cutoff: false + housing_benefit_applicable_amount: 238 * 52 + housing_benefit: 47.68 # 656.08 at the higher rate: 0.65 x (256 - 238) x 52 = 608.40 more diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/state_pension_age_date.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/state_pension_age_date.yaml new file mode 100644 index 0000000000..f9c1f230b3 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/state_pension_age_date.yaml @@ -0,0 +1,104 @@ +# The day State Pension age is attained, written as a YYYYMMDD number, at +# boundaries of Pensions Act 1995 Schedule 4 paragraph 1. Age and +# months_since_last_birthday place the date of birth named in each test at +# 6 October of the fiscal year. Expected days come from a day-level reading +# of the statute. + +- name: Woman born 5 April 1950 attains State Pension age on her 60th birthday + period: 2015 + input: + age: 65 + months_since_last_birthday: 6.0323 + is_male: false + output: + state_pension_age_date: 20100405 + +- name: Woman born 6 April 1950 attains it on 6 May 2010 (table 1) + period: 2015 + input: + age: 65 + months_since_last_birthday: 6.0 + is_male: false + output: + state_pension_age_date: 20100506 + +- name: Man born 5 December 1953 attains it on his 65th birthday + period: 2018 + input: + age: 64 + months_since_last_birthday: 10.03333 + is_male: true + output: + state_pension_age_date: 20181205 + +- name: Man born 6 December 1953 attains it on 6 March 2019 (table 2) + period: 2018 + input: + age: 64 + months_since_last_birthday: 10.0 + is_male: true + output: + state_pension_age_date: 20190306 + +- name: Man born 5 October 1954 attains it on 6 September 2020 (table 2) + period: 2020 + input: + age: 66 + months_since_last_birthday: 0.03333 + is_male: true + output: + state_pension_age_date: 20200906 + +- name: Woman born 6 October 1954 attains it on her 66th birthday + period: 2020 + input: + age: 66 + months_since_last_birthday: 0.0 + is_male: false + output: + state_pension_age_date: 20201006 + +- name: Woman born 31 March 1955 attains it on 31 March 2021, before the Housing Benefit cutoff + period: 2026 + input: + age: 71 + months_since_last_birthday: 6.19355 + is_male: false + output: + state_pension_age_date: 20210331 + +- name: Man born 1 April 1955 attains it on 1 April 2021, the Housing Benefit cutoff + period: 2026 + input: + age: 71 + months_since_last_birthday: 6.1613 + is_male: true + output: + state_pension_age_date: 20210401 + +- name: Person born 31 July 1960 attains it on 30 November 2026 (rule 7A) + period: 2026 + input: + age: 66 + months_since_last_birthday: 2.19355 + is_male: false + output: + state_pension_age_date: 20261130 + +- name: Person born 6 March 1961 attains it on their 67th birthday + period: 2028 + input: + age: 67 + months_since_last_birthday: 7.0 + is_male: true + output: + state_pension_age_date: 20280306 + +- name: Person born 6 April 1978 attains it on their 68th birthday + period: 2046 + input: + age: 68 + months_since_last_birthday: 6.0 + is_male: false + output: + state_pension_age_date: 20460406 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml new file mode 100644 index 0000000000..57718e6eca --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml @@ -0,0 +1,133 @@ +# Pension-age Council Tax Reduction personal allowances by country. England's +# prescribed requirements (SI 2012/2885 Sch 2 para 1, amended by SI 2021/29) give +# the lower rates where every member attained State Pension age on or after +# 1 April 2021; the Scottish (SSI 2012/319 Sch 1 para 2) and Welsh (SI 2013/3029 +# Sch 2 para 1) schemes have one pension-age rate. Amounts are for 2026-27. + +- name: In England, a single person who attained State Pension age on 31 March 2021 gets the higher rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 71 + months_since_last_birthday: 6.19355 + is_male: false + benunits: + benunit: + members: [claimant] + benefits_premiums: 0 + households: + household: + members: [claimant] + region: LONDON + output: + council_tax_reduction_applicable_amount: 256.00 * 52 + +- name: In England, a single person who attained State Pension age on 1 April 2021 gets the lower rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 71 + months_since_last_birthday: 6.1613 + is_male: false + benunits: + benunit: + members: [claimant] + benefits_premiums: 0 + households: + household: + members: [claimant] + region: NORTH_EAST + output: + council_tax_reduction_applicable_amount: 238.00 * 52 + +- name: In England, a couple who both attained State Pension age after April 2021 get the lower rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + months_since_last_birthday: 6.0 + is_male: true + younger: + age: 68 + months_since_last_birthday: 9.1613 + is_male: false + benunits: + benunit: + members: [older, younger] + benefits_premiums: 0 + households: + household: + members: [older, younger] + region: SOUTH_WEST + output: + council_tax_reduction_applicable_amount: 363.25 * 52 + +- name: In Scotland, a single person who attained State Pension age on 1 April 2021 keeps the one pension-age rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 71 + months_since_last_birthday: 6.1613 + is_male: false + benunits: + benunit: + members: [claimant] + benefits_premiums: 0 + households: + household: + members: [claimant] + region: SCOTLAND + output: + council_tax_reduction_applicable_amount: 256.00 * 52 + +- name: In Scotland, a couple who both attained State Pension age after April 2021 keep the one pension-age rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + months_since_last_birthday: 6.0 + is_male: true + younger: + age: 68 + months_since_last_birthday: 9.1613 + is_male: false + benunits: + benunit: + members: [older, younger] + benefits_premiums: 0 + households: + household: + members: [older, younger] + region: SCOTLAND + output: + council_tax_reduction_applicable_amount: 383.35 * 52 + +- name: In Wales, a single person who attained State Pension age on 1 April 2021 keeps the one pension-age rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 71 + months_since_last_birthday: 6.1613 + is_male: false + benunits: + benunit: + members: [claimant] + benefits_premiums: 0 + households: + household: + members: [claimant] + region: WALES + output: + council_tax_reduction_applicable_amount: 256.00 * 52 diff --git a/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py b/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py new file mode 100644 index 0000000000..368c3bf3b5 --- /dev/null +++ b/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py @@ -0,0 +1,187 @@ +"""Pension-age Housing Benefit personal allowances by when State Pension age +was attained. + +The Housing Benefit (Persons who have attained the qualifying age for state +pension credit) Regulations 2006 (SI 2006/214) Sch 3 para 1, as amended by SI +2021/188 from 1 April 2021 (Northern Ireland: SR 2006/406 Sch 4 para 1): a +single claimant or lone parent who attained pensionable age before 1 April +2021 gets the higher allowance in (1)(b), one who attained it on or after gets +(1)(c); a couple gets (2)(b) where one or both members attained it before, and +(2)(c) where both attained it on or after. + +Invariants, for any pension-age single person or couple in any year: + +1. Differential: the pension-age personal allowance in + housing_benefit_applicable_amount is the higher rate exactly when a member's + attainment day, read from the statute day by day + (test_state_pension_age.reference_attainment_day), is before 1 April 2021, + and the lower rate otherwise. +2. Bounds: the lower rate is below the higher rate in every year, including + uprated years, and lone parents get the single rates. +3. Monotonicity: among pension-age single people of one sex in one year, an + earlier date of birth never gives a lower allowance. +4. Before April 2021 there was one pension-age rate, which every pension-age + unit gets. +5. Council Tax Reduction: England's pensioner scheme has the same split and + amounts (SI 2012/2885 Sch 2 para 1, amended by SI 2021/29), so its + allowance equals Housing Benefit's; the Scottish and Welsh schemes have + one pension-age rate, the higher one. +""" + +from datetime import date, timedelta + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.system import system +from test_state_pension_age import grid_months, reference_attainment_day + +CUTOFF = date(2021, 4, 1) +YEARS = list(range(2019, 2031)) +P = system.parameters.gov.dwp.housing_benefit.allowances + + +def rates(year: int, couple: bool) -> tuple: + """(higher, lower) weekly pension-age allowance; lower is None before the + cutoff took effect.""" + node = P.couple if couple else P.single + at = f"{year}-06-01" + return node.aged(at), node.aged_from_cutoff(at) + + +def build(units: list, year: int, region: str = "LONDON") -> Simulation: + """Each unit is a list of (birth, male). Units are eligible with no + premiums, so the applicable amount is 52 times the personal allowance.""" + people, benunits = {}, {} + mid_year = grid_months(date(year, 10, 6)) + for u, members in enumerate(units): + names = [] + for k, (birth, male) in enumerate(members): + age_in_months = mid_year - grid_months(birth) + age = int(age_in_months // 12) + name = f"u{u}p{k}" + people[name] = { + "age": {year: age}, + "months_since_last_birthday": {year: age_in_months - 12 * age}, + "is_male": {year: male}, + } + names.append(name) + benunits[f"u{u}"] = { + "members": names, + "housing_benefit_eligible": {year: True}, + "benefits_premiums": {year: 0}, + } + return Simulation( + situation={ + "people": people, + "benunits": benunits, + "households": {"h": {"members": list(people), "region": {year: region}}}, + } + ) + + +def at_pension_age(members: list, year: int) -> bool: + mid_year = date(year, 10, 6) + return any(reference_attainment_day(b, m) <= mid_year for b, m in members) + + +def expected_allowance(members: list, year: int) -> float: + higher, lower = rates(year, couple=len(members) == 2) + attained = [reference_attainment_day(b, m) for b, m in members] + if lower is None or any(day < CUTOFF for day in attained): + return higher + return lower + + +def check(units: list, year: int) -> np.ndarray: + """Invariant 1 for every unit with a member over State Pension age.""" + amount = build(units, year).calculate("housing_benefit_applicable_amount", year) + weekly = amount / 52 + wrong = [ + (members, weekly[i], expected_allowance(members, year)) + for i, members in enumerate(units) + if at_pension_age(members, year) + and abs(weekly[i] - expected_allowance(members, year)) > 0.005 + ] + assert not wrong, wrong[:10] + return weekly + + +BIRTHS = [date(1938, 1, 1) + timedelta(days=d) for d in range(0, 23 * 366, 5)] +EDGES = [date(1955, 4, 1) + timedelta(days=shift) for shift in range(-3, 4)] + + +@pytest.mark.parametrize("year", YEARS) +def test_single_allowance_matches_statute_for_every_birth_date(year): + """Invariants 1, 3 and 4 for single people born every fifth day from 1938 + and either side of 1 April 1955, the birthday that reaches 66 on the + cutoff.""" + births = sorted(set(BIRTHS) | set(EDGES)) + for male in (True, False): + units = [[(birth, male)] for birth in births] + weekly = check(units, year) + eligible = [weekly[i] for i, u in enumerate(units) if at_pension_age(u, year)] + assert all(a >= b - 1e-6 for a, b in zip(eligible, eligible[1:])) + if year < CUTOFF.year: + assert np.allclose(eligible, rates(year, couple=False)[0]) + + +@pytest.mark.parametrize("year", range(2021, 2041)) +def test_lower_rates_are_below_higher_rates(year): + """Invariant 2, including years uprated past the last published rate.""" + for couple in (False, True): + higher, lower = rates(year, couple) + assert lower < higher + at = f"{year}-06-01" + assert P.lone_parent.aged_from_cutoff(at) == P.single.aged_from_cutoff(at) + + +MEMBER = st.tuples( + st.dates(min_value=date(1930, 1, 1), max_value=date(1962, 12, 31)), + st.booleans(), +) + + +@settings( + max_examples=15, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given( + units=st.lists(st.lists(MEMBER, min_size=1, max_size=2), min_size=1, max_size=25), + year=st.sampled_from(YEARS), +) +def test_any_single_or_couple_follows_the_cohort_rule(units, year): + """Invariant 1 for random single people and couples, including couples + where only one member is over State Pension age.""" + check(units, year) + + +@settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +@given( + units=st.lists(st.lists(MEMBER, min_size=1, max_size=2), min_size=1, max_size=20), + year=st.sampled_from(YEARS), + region=st.sampled_from(["LONDON", "NORTH_WEST", "SCOTLAND", "WALES"]), +) +def test_council_tax_reduction_splits_only_in_england(units, year, region): + """Invariant 5 for random single people and couples.""" + sim = build(units, year, region) + hb = sim.calculate("housing_benefit_applicable_amount", year) / 52 + ctr = sim.calculate("council_tax_reduction_applicable_amount", year) / 52 + for i, members in enumerate(units): + if not at_pension_age(members, year): + continue + if region in ("SCOTLAND", "WALES"): + expected = rates(year, couple=len(members) == 2)[0] + else: + expected = hb[i] + assert abs(ctr[i] - expected) < 0.005, (members, region, ctr[i], expected) diff --git a/policyengine_uk/tests/test_state_pension_age.py b/policyengine_uk/tests/test_state_pension_age.py index 12e8eced7d..aab0aaed42 100644 --- a/policyengine_uk/tests/test_state_pension_age.py +++ b/policyengine_uk/tests/test_state_pension_age.py @@ -263,6 +263,18 @@ def test_status_matches_statute_for_every_birth_date(year): assert not mismatches, mismatches[:10] +@pytest.mark.parametrize("year", YEARS) +def test_attainment_date_matches_statute_for_every_birth_date(year): + """state_pension_age_date is the day a day-level reading of the statute + gives, for every birth date in the differential set, and the same day + whichever year the person is placed from.""" + sim, keys = differential_simulation() + model = sim.calculate("state_pension_age_date", year) + expected = np.array([ymd(reference_attainment_day(b, m)) for b, m in keys]) + wrong = np.flatnonzero(model != expected) + assert not len(wrong), [(keys[i], model[i], expected[i]) for i in wrong[:10]] + + def mid_year_share(year: int, age: int, male: bool) -> float: """Share of people aged `age` on 6 October who have attained pensionable age by then, with birthdays spread evenly over the year.""" diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_applicable_amount.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_applicable_amount.py index 50c086675b..af23be37f1 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_applicable_amount.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_applicable_amount.py @@ -22,20 +22,39 @@ def formula(benunit, period, parameters): single = benunit("is_single_person", period) couple = benunit("is_couple", period) lone_parent = benunit("is_lone_parent", period) + # Pension-age allowances (SI 2006/214 Sch 3 para 1; NI: SR 2006/406 + # Sch 4 para 1). Since 1 April 2021 units whose members all attained + # State Pension age on or after the cutoff get the lower rates in + # sub-paragraphs (1)(c) and (2)(c); the rest keep the higher rates. + if p.pension_age_cutoff is None: + single_aged, couple_aged, lone_parent_aged = ( + p.single.aged, + p.couple.aged, + p.lone_parent.aged, + ) + else: + before_cutoff = benunit( + "housing_benefit_attained_pension_age_before_cutoff", period + ) + single_aged = where(before_cutoff, p.single.aged, p.single.aged_from_cutoff) + couple_aged = where(before_cutoff, p.couple.aged, p.couple.aged_from_cutoff) + lone_parent_aged = where( + before_cutoff, p.lone_parent.aged, p.lone_parent.aged_from_cutoff + ) single_personal_allowance = ( u_25 * p.single.younger + o_25 * p.single.older - + any_over_SP_age * p.single.aged + + any_over_SP_age * single_aged ) couple_personal_allowance = ( u_18 * p.couple.younger + o_18 * p.couple.older - + any_over_SP_age * p.couple.aged + + any_over_SP_age * couple_aged ) lone_parent_personal_allowance = ( u_18 * p.lone_parent.younger + o_18 * p.lone_parent.older - + any_over_SP_age * p.lone_parent.aged + + any_over_SP_age * lone_parent_aged ) personal_allowance = ( single * single_personal_allowance diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_attained_pension_age_before_cutoff.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_attained_pension_age_before_cutoff.py new file mode 100644 index 0000000000..20aa6699ca --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_attained_pension_age_before_cutoff.py @@ -0,0 +1,39 @@ +from policyengine_uk.model_api import * + + +class housing_benefit_attained_pension_age_before_cutoff(Variable): + value_type = bool + entity = BenUnit + label = "a member attained State Pension age before the Housing Benefit cutoff" + documentation = ( + "Whether any member of the benefit unit attained State Pension age " + "before the pension-age cutoff (1 April 2021). Such units keep the " + "higher pension-age personal allowance; units whose members all " + "attained it on or after the cutoff get the lower one. Before the " + "cutoff took effect there was a single pension-age allowance, the " + "higher one, so this is true for every unit then." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/214/schedule/3", + "https://www.legislation.gov.uk/uksi/2021/188/regulation/2/made", + "https://www.legislation.gov.uk/nisr/2006/406/schedule/4", + ) + + def formula(benunit, period, parameters): + cutoff = parameters( + period + ).gov.dwp.housing_benefit.allowances.pension_age_cutoff + if cutoff is None: + return np.ones(benunit.count, dtype=bool) + # SI 2006/214 Sch 3 para 1(2)(b) keeps the higher couple rate where + # "one member or both members" attained pensionable age before the + # cutoff; (2)(c) gives the lower one only where both attained it on or + # after. The regulations do not say which applies to a couple where one + # member has not yet attained it and the other did so after the cutoff + # (few such units remain on pension-age Housing Benefit). The + # explanatory note to SI 2021/188 keeps the uplift only where "one or + # more members ... attained pensionable age before 1st April 2021", so + # such couples get the lower rate here. + attained = benunit.members("state_pension_age_date", period) + return benunit.any(attained < cutoff) diff --git a/policyengine_uk/variables/gov/dwp/state_pension_age.py b/policyengine_uk/variables/gov/dwp/state_pension_age.py index f93a15799e..9296e4a9af 100644 --- a/policyengine_uk/variables/gov/dwp/state_pension_age.py +++ b/policyengine_uk/variables/gov/dwp/state_pension_age.py @@ -1,9 +1,7 @@ from policyengine_uk.model_api import * from policyengine_uk.utils.dates import ( - add_months_to_yyyymmdd, exact_age_in_months, grid_month, - grid_months_to_yyyymmdd, yyyymmdd_to_grid_months, ) @@ -26,35 +24,11 @@ class state_pension_age(Variable): ) def formula(person, period, parameters): - p = parameters(period).gov.dwp.state_pension.age # Dates are in months on a grid whose months start on the 6th, so the # middle of the fiscal year (6 October) is a whole month. - mid_year = grid_month(period.start.year, 10) age_in_months = exact_age_in_months( person("age", period), person("months_since_last_birthday", period) ) - birth = mid_year - age_in_months - # The day of birth: the one starting at or after that instant, so the - # person's legal age on 6 October is age. - birth_date = grid_months_to_yyyymmdd(birth) - - # Pensions Act 1995 Sch 4 para 1 rule (1): men born before 6 December - # 1953 attain pensionable age at 65. Everyone else follows the - # timetable in rules (2) to (10), which sets either an age or a day. - male_rule = person("is_male", period) & (birth_date < p.male.born_before) - age_rule = where(male_rule, p.male.age, p.age_by_birth_date.calc(birth_date)) - day_rule = where(male_rule, 0, p.day_by_birth_date.calc(birth_date)) - day_rule_month = where( - day_rule > 0, - yyyymmdd_to_grid_months(np.maximum(day_rule, 10101)), - -np.inf, - ) - # An age of N years and M months is attained at the commencement of - # the anniversary (Family Law Reform Act 1969 s.9(1)): the same day of - # the month, or the month's last day where that day does not exist, - # which also gives the three days in rule (7A). - anniversary = yyyymmdd_to_grid_months( - add_months_to_yyyymmdd(birth_date, age_rule) - ) - attained = np.maximum(anniversary, day_rule_month) + birth = grid_month(period.start.year, 10) - age_in_months + attained = yyyymmdd_to_grid_months(person("state_pension_age_date", period)) return (attained - birth) / 12 diff --git a/policyengine_uk/variables/gov/dwp/state_pension_age_date.py b/policyengine_uk/variables/gov/dwp/state_pension_age_date.py new file mode 100644 index 0000000000..1fbe6532ad --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/state_pension_age_date.py @@ -0,0 +1,51 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.dates import ( + add_months_to_yyyymmdd, + exact_age_in_months, + grid_month, + grid_months_to_yyyymmdd, +) + + +class state_pension_age_date(Variable): + value_type = int + entity = Person + label = "date this person attains State Pension age" + documentation = ( + "The day on which this person attains State Pension age (pensionable " + "age), written as a YYYYMMDD number. It is set by their date of birth, " + "which comes from age and months_since_last_birthday." + ) + definition_period = YEAR + unit = "date" + reference = ( + "https://www.legislation.gov.uk/ukpga/1995/26/schedule/4", + "https://www.legislation.gov.uk/ukpga/2014/19/section/26", + ) + + def formula(person, period, parameters): + p = parameters(period).gov.dwp.state_pension.age + # Dates are in months on a grid whose months start on the 6th, so the + # middle of the fiscal year (6 October) is a whole month. + age_in_months = exact_age_in_months( + person("age", period), person("months_since_last_birthday", period) + ) + # The day of birth: the one starting at or after that instant, so the + # person's legal age on 6 October is age. + birth_date = grid_months_to_yyyymmdd( + grid_month(period.start.year, 10) - age_in_months + ) + + # Pensions Act 1995 Sch 4 para 1 rule (1): men born before 6 December + # 1953 attain pensionable age at 65. Everyone else follows the + # timetable in rules (2) to (10), which sets either an age or a day. + male_rule = person("is_male", period) & (birth_date < p.male.born_before) + age_rule = where(male_rule, p.male.age, p.age_by_birth_date.calc(birth_date)) + day_rule = where(male_rule, 0, p.day_by_birth_date.calc(birth_date)) + # An age of N years and M months is attained at the commencement of + # the anniversary (Family Law Reform Act 1969 s.9(1)): the same day of + # the month, or the month's last day where that day does not exist, + # which also gives the three days in rule (7A). Where the statute sets + # a day instead, the day rule is later; otherwise it is zero. + anniversary = add_months_to_yyyymmdd(birth_date, age_rule) + return np.maximum(anniversary, day_rule) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_amount.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_amount.py index c096429589..f97b91c997 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_amount.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_amount.py @@ -1,4 +1,5 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.household.demographic.country import Country class council_tax_reduction_applicable_amount(Variable): @@ -21,20 +22,43 @@ def formula(benunit, period, parameters): single = benunit("is_single_person", period) couple = benunit("is_couple", period) lone_parent = benunit("is_lone_parent", period) + # Pension-age personal allowances. England's prescribed requirements + # (SI 2012/2885 Sch 2 para 1, amended by SI 2021/29 from 2021-22) split + # them by when State Pension age was attained, with the same cutoff and + # amounts as Housing Benefit. The Scottish (SSI 2012/319 Sch 1 para 2) + # and Welsh (SI 2013/3029 Sch 2 para 1) schemes keep one pension-age + # rate, the higher one. + single_aged, couple_aged, lone_parent_aged = ( + p.single.aged, + p.couple.aged, + p.lone_parent.aged, + ) + if p.pension_age_cutoff is not None: + in_england = benunit.any( + benunit.members.household("country", period) == Country.ENGLAND + ) + lower = in_england & ~benunit( + "housing_benefit_attained_pension_age_before_cutoff", period + ) + single_aged = where(lower, p.single.aged_from_cutoff, single_aged) + couple_aged = where(lower, p.couple.aged_from_cutoff, couple_aged) + lone_parent_aged = where( + lower, p.lone_parent.aged_from_cutoff, lone_parent_aged + ) single_personal_allowance = ( u_25 * p.single.younger + o_25 * p.single.older - + any_over_SP_age * p.single.aged + + any_over_SP_age * single_aged ) couple_personal_allowance = ( u_18 * p.couple.younger + o_18 * p.couple.older - + any_over_SP_age * p.couple.aged + + any_over_SP_age * couple_aged ) lone_parent_personal_allowance = ( u_18 * p.lone_parent.younger + o_18 * p.lone_parent.older - + any_over_SP_age * p.lone_parent.aged + + any_over_SP_age * lone_parent_aged ) personal_allowance = ( single * single_personal_allowance From f159ff49c89559a54a135ebf293bf57bfa780710 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 30 Sep 2026 08:41:25 -0400 Subject: [PATCH 2/7] Address review: mixed-age couples, pensionable age, statute table test - Explain why the mixed-age couple gap cannot arise for pension-age Housing Benefit in law, and why PolicyEngine gives such units the lower rate; add the same note for England's CTR rows (2) and (5). - Cite where "pensionable age" is defined (SSCBA 1992 s.122(1), applied by the Interpretation Act 1978 s.11) and why dates are compared as integers. - CTR reads the country from the benefit unit's household, as the CTR award formula does. - Test the lower rates and cutoff against the statute's table for 2021-22 to 2026-27, and England's CTR rate for a mixed-age couple. Co-Authored-By: Claude Opus 5.5 --- ...enefit_pension_age_personal_allowance.yaml | 3 +- ...uction_pension_age_personal_allowance.yaml | 24 ++++++++++++++ ...est_housing_benefit_pension_age_cohorts.py | 32 +++++++++++++++++-- ...efit_attained_pension_age_before_cutoff.py | 28 ++++++++++------ ...council_tax_reduction_applicable_amount.py | 17 +++++----- 5 files changed, 84 insertions(+), 20 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml index a6244d0f93..3a3cacfff9 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml @@ -3,7 +3,8 @@ # by SI 2021/188 from 1 April 2021. Higher rates (1)(b)/(2)(b) where a member # attained pensionable age before 1 April 2021; lower rates (1)(c)/(2)(c) where # every member attained it on or after. Amounts are the Up-rating Order rates. -# Age and months_since_last_birthday place each date of birth at 6 October. +# Each date of birth is placed by age and months_since_last_birthday, both +# measured at 6 October of the fiscal year. - name: Single woman who attained State Pension age on 31 March 2021 keeps the higher rate period: 2026 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml index 57718e6eca..ae22978388 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml @@ -131,3 +131,27 @@ region: WALES output: council_tax_reduction_applicable_amount: 256.00 * 52 + +- name: In England, a mixed-age couple whose older member attained State Pension age after April 2021 gets the lower rate + period: 2026 + absolute_error_margin: 0.01 + input: + people: + older: + age: 70 + months_since_last_birthday: 6.0 + is_male: true + younger: + age: 60 + months_since_last_birthday: 9.1613 + is_male: false + benunits: + benunit: + members: [older, younger] + benefits_premiums: 0 + households: + household: + members: [older, younger] + region: EAST_MIDLANDS + output: + council_tax_reduction_applicable_amount: 363.25 * 52 diff --git a/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py b/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py index 368c3bf3b5..93979e538c 100644 --- a/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py +++ b/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py @@ -22,7 +22,9 @@ earlier date of birth never gives a lower allowance. 4. Before April 2021 there was one pension-age rate, which every pension-age unit gets. -5. Council Tax Reduction: England's pensioner scheme has the same split and +5. The lower rates and the cutoff equal the statute's table for every year + 2021-22 to 2026-27 (hard-coded below from legislation.gov.uk). +6. Council Tax Reduction: England's pensioner scheme has the same split and amounts (SI 2012/2885 Sch 2 para 1, amended by SI 2021/29), so its allowance equals Housing Benefit's; the Scottish and Welsh schemes have one pension-age rate, the higher one. @@ -173,7 +175,7 @@ def test_any_single_or_couple_follows_the_cohort_rule(units, year): region=st.sampled_from(["LONDON", "NORTH_WEST", "SCOTLAND", "WALES"]), ) def test_council_tax_reduction_splits_only_in_england(units, year, region): - """Invariant 5 for random single people and couples.""" + """Invariant 6 for random single people and couples.""" sim = build(units, year, region) hb = sim.calculate("housing_benefit_applicable_amount", year) / 52 ctr = sim.calculate("council_tax_reduction_applicable_amount", year) / 52 @@ -185,3 +187,29 @@ def test_council_tax_reduction_splits_only_in_england(units, year, region): else: expected = hb[i] assert abs(ctr[i] - expected) < 0.005, (members, region, ctr[i], expected) + + +# SI 2006/214 Sch 3 para 1(1)(c) and (2)(c), weekly: inserted by SI 2021/188 reg +# 2(3) from 1 April 2021, then set by the Up-rating Orders SI 2022/292, +# 2023/316, 2024/242, 2025/295 and 2026/148 (legislation.gov.uk point-in-time +# texts at 11 April 2022, 10 April 2023, 8 April 2024, 7 April 2025 and the +# current text). +STATUTE_LOWER_RATES = { + 2021: (177.10, 270.30), + 2022: (182.60, 278.70), + 2023: (201.05, 306.85), + 2024: (218.15, 332.95), + 2025: (227.10, 346.60), + 2026: (238.00, 363.25), +} + + +@pytest.mark.parametrize("year", sorted(STATUTE_LOWER_RATES)) +def test_lower_rates_and_cutoff_match_the_statute(year): + """Invariant 5.""" + single, couple = STATUTE_LOWER_RATES[year] + at = f"{year}-06-01" + assert P.single.aged_from_cutoff(at) == pytest.approx(single, abs=1e-6) + assert P.lone_parent.aged_from_cutoff(at) == pytest.approx(single, abs=1e-6) + assert P.couple.aged_from_cutoff(at) == pytest.approx(couple, abs=1e-6) + assert P.pension_age_cutoff(at) == 20210401 diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_attained_pension_age_before_cutoff.py b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_attained_pension_age_before_cutoff.py index 20aa6699ca..6869eac8b3 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_attained_pension_age_before_cutoff.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/housing_benefit_attained_pension_age_before_cutoff.py @@ -26,14 +26,24 @@ def formula(benunit, period, parameters): ).gov.dwp.housing_benefit.allowances.pension_age_cutoff if cutoff is None: return np.ones(benunit.count, dtype=bool) - # SI 2006/214 Sch 3 para 1(2)(b) keeps the higher couple rate where - # "one member or both members" attained pensionable age before the - # cutoff; (2)(c) gives the lower one only where both attained it on or - # after. The regulations do not say which applies to a couple where one - # member has not yet attained it and the other did so after the cutoff - # (few such units remain on pension-age Housing Benefit). The - # explanatory note to SI 2021/188 keeps the uplift only where "one or - # more members ... attained pensionable age before 1st April 2021", so - # such couples get the lower rate here. + # "Pensionable age" is State Pension age under Pensions Act 1995 Sch 4 + # para 1 (SSCBA 1992 s.122(1), applied to these regulations by the + # Interpretation Act 1978 s.11), which state_pension_age_date follows. + # + # Para 1(2)(b) keeps the higher couple rate where "one member or both + # members" attained pensionable age before the cutoff, and (2)(c) gives + # the lower one where "both members" attained it on or after. Neither + # covers a couple where one member has not reached it and the other did + # so after the cutoff. In law that couple cannot be on pension-age + # Housing Benefit: mixed-age couples keep it only under the savings for + # awards from before 15 May 2019 (SI 2019/37 art 4), when the older + # member had already reached it. PolicyEngine can still give such a + # unit pension-age rates through a reported continuing award; it gets + # the lower rate, since the explanatory note to SI 2021/188 keeps the + # uplift only where "one or more members ... attained pensionable age + # before 1st April 2021". + # + # Dates are compared as YYYYMMDD integers, which are exact; as float32 + # they would not be (20210401 would round to 20210400). attained = benunit.members("state_pension_age_date", period) return benunit.any(attained < cutoff) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_amount.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_amount.py index f97b91c997..cd47276884 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_amount.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_amount.py @@ -23,20 +23,21 @@ def formula(benunit, period, parameters): couple = benunit("is_couple", period) lone_parent = benunit("is_lone_parent", period) # Pension-age personal allowances. England's prescribed requirements - # (SI 2012/2885 Sch 2 para 1, amended by SI 2021/29 from 2021-22) split - # them by when State Pension age was attained, with the same cutoff and - # amounts as Housing Benefit. The Scottish (SSI 2012/319 Sch 1 para 2) - # and Welsh (SI 2013/3029 Sch 2 para 1) schemes keep one pension-age - # rate, the higher one. + # (SI 2012/2885 Sch 2 para 1, amended by SI 2021/29 for schemes for + # financial years from 2021-22) split them by when State Pension age + # was attained, with the same cutoff and amounts as Housing Benefit. + # The Scottish (SSI 2012/319 Sch 1 para 2) and Welsh (SI 2013/3029 + # Sch 2 para 1) schemes keep one pension-age rate, the higher one. + # England's rows (2) and (5) do not cover a couple where one member has + # not reached State Pension age and the other did so after the cutoff; + # as for Housing Benefit, such a couple gets the lower rate. single_aged, couple_aged, lone_parent_aged = ( p.single.aged, p.couple.aged, p.lone_parent.aged, ) if p.pension_age_cutoff is not None: - in_england = benunit.any( - benunit.members.household("country", period) == Country.ENGLAND - ) + in_england = benunit.household("country", period) == Country.ENGLAND lower = in_england & ~benunit( "housing_benefit_attained_pension_age_before_cutoff", period ) From cfa65f384d61695c2877557e03cf347d0665fb5f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 3 Oct 2026 17:04:22 -0400 Subject: [PATCH 3/7] Keep main's pension-age tests on the higher rate; update the reported household MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Main's new Pension Credit carer-income and England CTR tests use pensioners aged 70 in 2026, who reached State Pension age after 1 April 2021 and so get the lower allowance under this PR. Their arithmetic assumes the higher rate, so they now use pensioners aged 72 (State Pension age reached in 2019). The reported household's £47.68 relied on a £365.20 income disregard that main's Housing Benefit earnings-disregard fix (#1908) removed: with no earnings there is no disregard. Its HB at the lower rate is now nil; a council-tenant case with £12,000 rent shows the 608.40 gap. Co-Authored-By: Claude Opus 5.5 --- ...enefit_pension_age_personal_allowance.yaml | 31 ++++++++++++++++++- .../pension_credit_carer_income.yaml | 26 ++++++++++------ .../council_tax_reduction_pension_credit.yaml | 16 +++++++--- ...eduction_pensioner_earnings_disregard.yaml | 4 ++- 4 files changed, 61 insertions(+), 16 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml index 3a3cacfff9..457c2e0e45 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml @@ -260,6 +260,10 @@ housing_benefit_attained_pension_age_before_cutoff: true - name: Private renter aged 70 with a £30,000 State Pension is assessed at the lower rate + # The reported household. Income is 30,000 less 3,486 income tax; with no + # earnings there is no earnings disregard. At the lower rate, + # 9,000 - 0.65 x (26,514 - 238 x 52) is below zero. The higher rate would + # give 9,000 - 0.65 x (26,514 - 256 x 52) = 418.70. period: 2026 absolute_error_margin: 0.01 input: @@ -278,4 +282,29 @@ output: housing_benefit_attained_pension_age_before_cutoff: false housing_benefit_applicable_amount: 238 * 52 - housing_benefit: 47.68 # 656.08 at the higher rate: 0.65 x (256 - 238) x 52 = 608.40 more + housing_benefit_applicable_income: 26_514 + housing_benefit: 0 + +- name: A council tenant aged 70 with the same income and £12,000 rent gets 608.40 a year less than at the higher rate + # A council tenancy, so the Local Housing Allowance does not cap the rent. + # 12,000 - 0.65 x (26,514 - 238 x 52) = 2,810.30; at the higher rate it would + # be 3,418.70, so the lower rate costs 0.65 x (256 - 238) x 52 = 608.40. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person: + age: 70 + state_pension_reported: 30_000 + benunits: + benunit: + members: [person] + households: + household: + members: [person] + rent: 12_000 + tenure_type: RENT_FROM_COUNCIL + output: + housing_benefit_attained_pension_age_before_cutoff: false + housing_benefit_applicable_amount: 238 * 52 + housing_benefit: 2_810.30 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_carer_income.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_carer_income.yaml index 15ac062765..d8da059e6f 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_carer_income.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_carer_income.yaml @@ -159,6 +159,8 @@ savings_credit: 0.6 * (238 - 208.07) * 52 - name: Housing Benefit and Council Tax Reduction no longer fall where Guarantee Credit used to end for a couple whose partner receives Carer's Allowance + # Aged 72: State Pension age was reached in 2019, before 1 April 2021, so + # the higher couple allowance applies (SI 2006/214 Sch 3 para 1(2)(b)). # Before #1952, Pension Credit left out the partner's Carer's Allowance, so # Guarantee Credit ran on to a private pension of 9,700 and the Housing # Benefit and Council Tax Reduction passports ended at 9,800, when both @@ -171,19 +173,19 @@ input: people: claimant_1: - age: 70 + age: 72 state_pension: 13_500 private_pension_income: 9_700 carer_1: - age: 70 + age: 72 state_pension: 0 care_hours: 35 claimant_2: - age: 70 + age: 72 state_pension: 13_500 private_pension_income: 9_800 carer_2: - age: 70 + age: 72 state_pension: 0 care_hours: 35 benunits: @@ -225,6 +227,8 @@ council_tax_benefit: [1_323.32, 1_307.32] - name: Net income rises where Guarantee Credit now ends for a couple whose partner receives Carer's Allowance + # Aged 72: State Pension age was reached in 2019, before 1 April 2021, so + # the higher couple allowance applies (SI 2006/214 Sch 3 para 1(2)(b)). # Guarantee Credit ends at a private pension of about 4,164 (income 21,392.80 # = the minimum guarantee). Across 4,100 to 4,200 net income rises by # 100 - 20 tax - 51.40 Guarantee Credit = 28.60: Housing Benefit and Council @@ -235,19 +239,19 @@ input: people: claimant_1: - age: 70 + age: 72 state_pension: 13_500 private_pension_income: 4_100 carer_1: - age: 70 + age: 72 state_pension: 0 care_hours: 35 claimant_2: - age: 70 + age: 72 state_pension: 13_500 private_pension_income: 4_200 carer_2: - age: 70 + age: 72 state_pension: 0 care_hours: 35 benunits: @@ -282,6 +286,8 @@ council_tax_benefit: [2_000, 2_000] - name: Pension-age Housing Benefit counts Carer Support Payment but not the Scottish Carer Supplement + # Aged 72: State Pension age was reached in 2019, before 1 April 2021, so + # the higher couple allowance applies (SI 2006/214 Sch 3 para 1(2)(b)). # HB (SPC) Regs 2006 reg 29(1)(j)(xviiha), inserted by SI 2026/246 art 21(3). # Income is above the minimum guarantee, so no Guarantee Credit passport. # Nobody pays income tax: 12,000 and 6,000 + 5,103.80 are each below the @@ -291,11 +297,11 @@ input: people: claimant: - age: 70 + age: 72 state_pension: 0 private_pension_income: 12_000 carer: - age: 70 + age: 72 state_pension: 0 private_pension_income: 6_000 care_hours: 35 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_credit.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_credit.yaml index 6e52f66d3d..917acda226 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_credit.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_credit.yaml @@ -13,12 +13,14 @@ # central path in 2039-40, where the State Pension figures below come from. - name: England guarantee credit recipient with income above the applicable amount gets maximum CTR + # Aged 72: State Pension age was reached in 2019, before 1 April 2021, so + # the higher personal allowance applies (SI 2012/2885 Sch 2 para 1). period: 2026 absolute_error_margin: 0.01 input: people: pensioner: - age: 70 + age: 72 state_pension: 18_712 benunits: benunit: @@ -73,12 +75,14 @@ council_tax_reduction: 2_000 - name: Entitled to guarantee credit but not claiming Pension Credit keeps the taper + # Aged 72: State Pension age was reached in 2019, before 1 April 2021, so + # the higher personal allowance applies (SI 2012/2885 Sch 2 para 1). period: 2026 absolute_error_margin: 0.01 input: people: pensioner: - age: 70 + age: 72 state_pension: 18_712 benunits: benunit: @@ -103,12 +107,14 @@ council_tax_reduction: 1_165.68 - name: England pensioner without Pension Credit keeps the taper + # Aged 72: State Pension age was reached in 2019, before 1 April 2021, so + # the higher personal allowance applies (SI 2012/2885 Sch 2 para 1). period: 2026 absolute_error_margin: 0.01 input: people: pensioner: - age: 70 + age: 72 state_pension: 18_712 benunits: benunit: @@ -508,12 +514,14 @@ council_tax_reduction: 1_999.60 - name: Capital of exactly 16,000 is within the limit for a pensioner without Pension Credit + # Aged 72: State Pension age was reached in 2019, before 1 April 2021, so + # the higher personal allowance applies (SI 2012/2885 Sch 2 para 1). period: 2026 absolute_error_margin: 0.01 input: people: pensioner: - age: 70 + age: 72 state_pension: 13_000 benunits: benunit: diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner_earnings_disregard.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner_earnings_disregard.yaml index c077000081..1f42bf1d23 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner_earnings_disregard.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner_earnings_disregard.yaml @@ -419,12 +419,14 @@ council_tax_reduction_pensioner_earnings_disregard: 0 - name: England pensioner with State Pension and earnings has 5 a week of earnings disregarded in CTR + # Aged 72: State Pension age was reached in 2019, before 1 April 2021, so + # the higher personal allowance applies (SI 2012/2885 Sch 2 para 1). period: 2026 absolute_error_margin: 0.01 input: people: pensioner: - age: 70 + age: 72 state_pension: 15_000 employment_income: 1_000 benunits: From aed099ca168590efaee04634ba23395d683d9c0c Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 18:08:15 -0400 Subject: [PATCH 4/7] Read the attainment date from birth_instant, and test it against state_pension_age state_pension_age_date now takes the day of birth from birth_instant, as state_pension_age does, so a given date_of_birth is respected. A differential test checks that the age implied by the attainment date is state_pension_age. Co-Authored-By: Claude Opus 5.5 --- .../tests/test_state_pension_age.py | 20 ++++++++++++++++++ .../gov/dwp/state_pension_age_date.py | 21 +++++++------------ 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/policyengine_uk/tests/test_state_pension_age.py b/policyengine_uk/tests/test_state_pension_age.py index fa83514c32..7cb1558388 100644 --- a/policyengine_uk/tests/test_state_pension_age.py +++ b/policyengine_uk/tests/test_state_pension_age.py @@ -303,6 +303,26 @@ def test_attainment_date_matches_statute_for_every_birth_date(year): assert not len(wrong), [(keys[i], model[i], expected[i]) for i in wrong[:10]] +@pytest.mark.parametrize("year", [2021, 2026, 2030]) +def test_attainment_date_agrees_with_state_pension_age(year): + """Differential: state_pension_age_date and state_pension_age read the + same timetable from the same instant of birth, so the age implied by the + attainment date is state_pension_age.""" + from policyengine_uk.utils.dates import birth_instant_from_age + from policyengine_uk.utils.dates import yyyymmdd_to_grid_months + + sim, keys = differential_simulation() + attained = yyyymmdd_to_grid_months(sim.calculate("state_pension_age_date", year)) + birth = birth_instant_from_age( + year, + sim.calculate("age", year).values, + sim.calculate("months_since_last_birthday", year).values, + ) + implied = (attained - birth) / 12 + model = sim.calculate("state_pension_age", year).values + np.testing.assert_allclose(implied, model, atol=1e-9) + + def mid_year_share(year: int, age: int, male: bool) -> float: """Share of people aged `age` on 6 October who have attained pensionable age by then, with birthdays spread evenly over the year.""" diff --git a/policyengine_uk/variables/gov/dwp/state_pension_age_date.py b/policyengine_uk/variables/gov/dwp/state_pension_age_date.py index 1fbe6532ad..d343cfd694 100644 --- a/policyengine_uk/variables/gov/dwp/state_pension_age_date.py +++ b/policyengine_uk/variables/gov/dwp/state_pension_age_date.py @@ -1,8 +1,7 @@ from policyengine_uk.model_api import * from policyengine_uk.utils.dates import ( add_months_to_yyyymmdd, - exact_age_in_months, - grid_month, + birth_instant, grid_months_to_yyyymmdd, ) @@ -13,8 +12,9 @@ class state_pension_age_date(Variable): label = "date this person attains State Pension age" documentation = ( "The day on which this person attains State Pension age (pensionable " - "age), written as a YYYYMMDD number. It is set by their date of birth, " - "which comes from age and months_since_last_birthday." + "age), written as a YYYYMMDD number. It is set by their date of birth: " + "date_of_birth where given, otherwise from age and " + "months_since_last_birthday." ) definition_period = YEAR unit = "date" @@ -25,16 +25,9 @@ class state_pension_age_date(Variable): def formula(person, period, parameters): p = parameters(period).gov.dwp.state_pension.age - # Dates are in months on a grid whose months start on the 6th, so the - # middle of the fiscal year (6 October) is a whole month. - age_in_months = exact_age_in_months( - person("age", period), person("months_since_last_birthday", period) - ) - # The day of birth: the one starting at or after that instant, so the - # person's legal age on 6 October is age. - birth_date = grid_months_to_yyyymmdd( - grid_month(period.start.year, 10) - age_in_months - ) + # The day of birth, read as state_pension_age reads it: the day + # starting at or after the instant of birth (birth_instant). + birth_date = grid_months_to_yyyymmdd(birth_instant(person, period)) # Pensions Act 1995 Sch 4 para 1 rule (1): men born before 6 December # 1953 attain pensionable age at 65. Everyone else follows the From 8b696977b42228697ef46c4a548e7021917fe072 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 18:59:39 -0400 Subject: [PATCH 5/7] Keep tests on the pension-age tables under main's regulation switches Main now puts a mixed-age couple entitled to Universal Credit in the working-age tables (HB reg 5; CTR pensioner test). The mixed-age cohort tests set the pension-age switch, as for a protected couple with no Universal Credit. Main's new England CTR tests with pensioners aged 70 now use pensioners aged 72, who reached State Pension age in 2019, since their arithmetic assumes the higher allowance. Co-Authored-By: Claude Opus 5.5 --- .../housing_benefit_pension_age_personal_allowance.yaml | 6 ++++++ .../council_tax_reduction/claimant_family_scheme.yaml | 4 +++- .../council_tax_reduction_carer_support_payment.yaml | 4 +++- ...ouncil_tax_reduction_pension_age_personal_allowance.yaml | 3 +++ policyengine_uk/tests/test_state_pension_age.py | 6 +++--- 5 files changed, 18 insertions(+), 5 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml index 457c2e0e45..9150b62ff1 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/housing_benefit_pension_age_personal_allowance.yaml @@ -197,6 +197,9 @@ benunit: members: [older, younger] housing_benefit_eligible: true + # A protected mixed-age couple with no Universal Credit, so the + # pension-age regulations apply (HB (SPC) Regs 2006 reg 5). + housing_benefit_pension_age_regulations_apply: true benefits_premiums: 0 output: housing_benefit_attained_pension_age_before_cutoff: false @@ -219,6 +222,9 @@ benunit: members: [older, younger] housing_benefit_eligible: true + # A protected mixed-age couple with no Universal Credit, so the + # pension-age regulations apply (HB (SPC) Regs 2006 reg 5). + housing_benefit_pension_age_regulations_apply: true benefits_premiums: 0 output: housing_benefit_attained_pension_age_before_cutoff: true diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_family_scheme.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_family_scheme.yaml index 793efe1508..ec70eab7ab 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_family_scheme.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/claimant_family_scheme.yaml @@ -53,6 +53,8 @@ council_tax_reduction: 1_575 - name: Oxford, working-age head and pension-age sharer each claim under their own scheme + # Aged 72: State Pension age was reached in 2019, before 1 April 2021, so + # the higher personal allowance applies (SI 2012/2885 Sch 2 para 1). period: 2026 absolute_error_margin: 0.01 input: @@ -61,7 +63,7 @@ age: 40 is_household_head: true sharer: - age: 70 + age: 72 benunits: head_family: members: [head] diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml index eefa9a2954..3c6e9790e4 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml @@ -412,12 +412,14 @@ council_tax_reduction: 2_000 - name: An English pensioner carer's Council Tax Reduction is unchanged + # Aged 72: State Pension age was reached in 2019, before 1 April 2021, so + # the higher personal allowance applies (SI 2012/2885 Sch 2 para 1). period: 2026 absolute_error_margin: 0.01 input: people: carer: - age: 70 + age: 72 state_pension: 0 private_pension_income: 14_000 care_hours: 35 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml index ae22978388..60cd801878 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pension_age_personal_allowance.yaml @@ -149,6 +149,9 @@ benunit: members: [older, younger] benefits_premiums: 0 + # A mixed-age couple with no Universal Credit claims as pensioners + # (SI 2012/2885 reg 3(1)(a)). + council_tax_reduction_pensioner: true households: household: members: [older, younger] diff --git a/policyengine_uk/tests/test_state_pension_age.py b/policyengine_uk/tests/test_state_pension_age.py index 7cb1558388..67541b5a61 100644 --- a/policyengine_uk/tests/test_state_pension_age.py +++ b/policyengine_uk/tests/test_state_pension_age.py @@ -315,11 +315,11 @@ def test_attainment_date_agrees_with_state_pension_age(year): attained = yyyymmdd_to_grid_months(sim.calculate("state_pension_age_date", year)) birth = birth_instant_from_age( year, - sim.calculate("age", year).values, - sim.calculate("months_since_last_birthday", year).values, + sim.calculate("age", year), + sim.calculate("months_since_last_birthday", year), ) implied = (attained - birth) / 12 - model = sim.calculate("state_pension_age", year).values + model = sim.calculate("state_pension_age", year) np.testing.assert_allclose(implied, model, atol=1e-9) From ddab487aa94f6bd06ec687c106224a42aaa53e10 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 10 Oct 2026 22:09:40 -0400 Subject: [PATCH 6/7] Fix the two pytest failures CI found at 8b696977 - test_housing_benefit_pension_age_cohorts: the property tests now set the pension-age HB and CTR switches from the statute, so a mixed-age couple that main would put on Universal Credit still tests the cohort rule. - test_state_pension_credit_qualifying_age: from April 2021, England's pension-age CTR allowance can be either rate, so the consumer check accepts the lower rate too (SI 2012/2885 Sch 2 para 1). Co-Authored-By: Claude Opus 5.5 --- ...est_housing_benefit_pension_age_cohorts.py | 11 +++++++++- ...est_state_pension_credit_qualifying_age.py | 20 ++++++++++--------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py b/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py index 93979e538c..78378d30ba 100644 --- a/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py +++ b/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py @@ -56,7 +56,13 @@ def rates(year: int, couple: bool) -> tuple: def build(units: list, year: int, region: str = "LONDON") -> Simulation: """Each unit is a list of (birth, male). Units are eligible with no - premiums, so the applicable amount is 52 times the personal allowance.""" + premiums, so the applicable amount is 52 times the personal allowance. + + Which table applies is set here, not left to the model: a unit with a + member over State Pension age is under the pension-age Housing Benefit + regulations and is a Council Tax Reduction pensioner. That leaves the + cohort rule as the only thing under test (a mixed-age couple on Universal + Credit would otherwise be in the working-age tables).""" people, benunits = {}, {} mid_year = grid_months(date(year, 10, 6)) for u, members in enumerate(units): @@ -71,9 +77,12 @@ def build(units: list, year: int, region: str = "LONDON") -> Simulation: "is_male": {year: male}, } names.append(name) + pension_age = at_pension_age(members, year) benunits[f"u{u}"] = { "members": names, "housing_benefit_eligible": {year: True}, + "housing_benefit_pension_age_regulations_apply": {year: pension_age}, + "council_tax_reduction_pensioner": {year: pension_age}, "benefits_premiums": {year: 0}, } return Simulation( diff --git a/policyengine_uk/tests/test_state_pension_credit_qualifying_age.py b/policyengine_uk/tests/test_state_pension_credit_qualifying_age.py index a1eea4e249..ad6995b459 100644 --- a/policyengine_uk/tests/test_state_pension_credit_qualifying_age.py +++ b/policyengine_uk/tests/test_state_pension_credit_qualifying_age.py @@ -185,16 +185,18 @@ def assert_consumers_follow_the_qualifying_age(births, male, year): sim.calculate("council_tax_reduction_pensioner", year), attained ) assert np.array_equal(sim.calculate("is_benefit_cap_exempt_other", year), attained) - aged = sim.tax_benefit_system.parameters( + allowances = sim.tax_benefit_system.parameters( f"{year}-06-01" - ).gov.dwp.housing_benefit.allowances.single.aged - assert np.array_equal( - np.isclose( - sim.calculate("council_tax_reduction_applicable_amount", year), - aged * WEEKS_IN_YEAR, - ), - attained, - ) + ).gov.dwp.housing_benefit.allowances + amount = sim.calculate("council_tax_reduction_applicable_amount", year) + at_pension_age_rate = np.isclose(amount, allowances.single.aged * WEEKS_IN_YEAR) + if allowances.pension_age_cutoff is not None: + # England's lower rate for those who attained State Pension age on or + # after 1 April 2021 (SI 2012/2885 Sch 2 para 1). + at_pension_age_rate |= np.isclose( + amount, allowances.single.aged_from_cutoff * WEEKS_IN_YEAR + ) + assert np.array_equal(at_pension_age_rate, attained) if year <= 2023: assert np.array_equal( sim.calculate("winter_fuel_allowance", year) > 0, attained From 7e2c1f0d87752dfcf89661a8800e017b72191bf3 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sun, 11 Oct 2026 15:09:07 -0400 Subject: [PATCH 7/7] Pin the higher pension-age rates to the statute; #1925 review comment fixes - test_housing_benefit_pension_age_cohorts: STATUTE_HIGHER_RATES holds SI 2006/214 Sch 3 para 1(1)(b)/(2)(b) for 2021-22 to 2026-27, now that #1925 set them to the published amounts, so both rates are hard-coded from law. - From #1925's review: the rates fixture header and test docstring say 2026-27 has only its own edition so far (and name UC's 2021-22 exception), and a CTR test comment shows 7,199.40 and 1,173.56. Co-Authored-By: Claude Opus 5.5 --- .../dwp_benefit_and_pension_rates.yaml | 18 ++++++------ ...l_tax_reduction_carer_support_payment.yaml | 2 +- .../tests/test_dwp_published_rates.py | 9 ++++-- ...est_housing_benefit_pension_age_cohorts.py | 29 +++++++++++++++++-- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/policyengine_uk/tests/fixtures/dwp_benefit_and_pension_rates.yaml b/policyengine_uk/tests/fixtures/dwp_benefit_and_pension_rates.yaml index 37cf87bfda..80e5b9fd16 100644 --- a/policyengine_uk/tests/fixtures/dwp_benefit_and_pension_rates.yaml +++ b/policyengine_uk/tests/fixtures/dwp_benefit_and_pension_rates.yaml @@ -1,15 +1,15 @@ # DWP "Benefit and pension rates" (and HMRC tax credit rates), by fiscal year -# (2025 = 2025-26), in each parameter's own unit and period. Each year's rate -# appears in that year's publication and as the previous-year column of the -# next year's, and the two agree, except UC's 2021-22 amounts: the temporary +# (2025 = 2025-26), in each parameter's own unit and period. Each year's rate to +# 2025-26 appears in that year's publication and as the previous-year column of +# the next year's, and the two agree, except UC's 2021-22 amounts: the temporary # uplift ended in October 2021, and the 2022-23 edition's previous-year column # gives the later amounts. The fixture holds the April 2021 amounts from the -# 2021-22 edition, which PolicyEngine reads at 30 April. For 2025-26 and -# 2026-27 the amounts match the -# Social Security Benefits Up-rating Orders 2025 (SI 2025/295) and 2026 (SI -# 2026/148), except the 2026-27 income-related ESA personal allowances, which -# are set under the Universal Credit Act 2025 s.5. Rows marked `derived` are -# computed from published rows as stated. Read by test_dwp_published_rates.py. +# 2021-22 edition, which PolicyEngine reads at 30 April. 2026-27 has only its +# own edition so far. For 2025-26 and 2026-27 the amounts match the Social +# Security Benefits Up-rating Orders 2025 (SI 2025/295) and 2026 (SI 2026/148), +# except the 2026-27 income-related ESA personal allowances, which are set under +# the Universal Credit Act 2025 s.5. Rows marked `derived` are computed from +# published rows as stated. Read by test_dwp_published_rates.py. sources: 2015: https://www.gov.uk/government/publications/proposed-benefit-and-pension-rates-2015-to-2016 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml index c944aaf56c..f18404306e 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_carer_support_payment.yaml @@ -217,7 +217,7 @@ scottish_carer_supplement: 0 council_tax_reduction_applicable_income: 7_000 + 83.30 * 52 council_tax_reduction_applicable_amount: (92.05 + 46.40) * 52 - # 2,000 - 0.2 x (11,331.60 - 7,198.88) = 1,173.46 + # 2,000 - 0.2 x (11,331.60 - 7,199.40) = 1,173.56 council_tax_reduction: 2_000 - 0.2 * (7_000 + 83.30 * 52 - (92.05 + 46.40) * 52) - name: In 2024 the Scottish carer is still on Carer's Allowance, which was already counted diff --git a/policyengine_uk/tests/test_dwp_published_rates.py b/policyengine_uk/tests/test_dwp_published_rates.py index f4c60567ca..3d52246ff1 100644 --- a/policyengine_uk/tests/test_dwp_published_rates.py +++ b/policyengine_uk/tests/test_dwp_published_rates.py @@ -2,9 +2,12 @@ The fixture fixtures/dwp_benefit_and_pension_rates.yaml lists, for each parameter with a published counterpart, the rate for each fiscal year as -published by DWP (and HMRC for tax credits). Each year appears in two -documents, that year's and the next year's, and the fixture holds the value -both agree on. Values are in the parameter's own unit and period. +published by DWP (and HMRC for tax credits). Each year to 2025-26 appears in +two documents, that year's and the next year's, and the fixture holds the +value both agree on; for UC's 2021-22 amounts, which changed in October 2021, +it holds the April amounts. 2026-27 has only its own edition so far and is +checked against SI 2026/148. Values are in the parameter's own unit and +period. Invariants: diff --git a/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py b/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py index 78378d30ba..42e1d54d7c 100644 --- a/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py +++ b/policyengine_uk/tests/test_housing_benefit_pension_age_cohorts.py @@ -22,8 +22,9 @@ earlier date of birth never gives a lower allowance. 4. Before April 2021 there was one pension-age rate, which every pension-age unit gets. -5. The lower rates and the cutoff equal the statute's table for every year - 2021-22 to 2026-27 (hard-coded below from legislation.gov.uk). +5. The lower rates, the higher rates and the cutoff equal the statute's table + for every year 2021-22 to 2026-27 (hard-coded below from legislation.gov.uk + and DWP's Benefit and pension rates). 6. Council Tax Reduction: England's pensioner scheme has the same split and amounts (SI 2012/2885 Sch 2 para 1, amended by SI 2021/29), so its allowance equals Housing Benefit's; the Scottish and Welsh schemes have @@ -222,3 +223,27 @@ def test_lower_rates_and_cutoff_match_the_statute(year): assert P.lone_parent.aged_from_cutoff(at) == pytest.approx(single, abs=1e-6) assert P.couple.aged_from_cutoff(at) == pytest.approx(couple, abs=1e-6) assert P.pension_age_cutoff(at) == 20210401 + + +# SI 2006/214 Sch 3 para 1(1)(b) and (2)(b), weekly, for those who attained State +# Pension age before 1 April 2021, set by the Up-rating Orders SI 2021/162, +# 2022/292, 2023/316, 2024/242, 2025/295 and 2026/148; the same amounts are in +# DWP's Benefit and pension rates for each year. +STATUTE_HIGHER_RATES = { + 2021: (191.15, 286.05), + 2022: (197.10, 294.90), + 2023: (217.00, 324.70), + 2024: (235.20, 352.00), + 2025: (244.40, 366.00), + 2026: (256.00, 383.35), +} + + +@pytest.mark.parametrize("year", sorted(STATUTE_HIGHER_RATES)) +def test_higher_rates_match_the_statute(year): + """Invariant 5.""" + single, couple = STATUTE_HIGHER_RATES[year] + at = f"{year}-06-01" + assert P.single.aged(at) == pytest.approx(single, abs=1e-6) + assert P.lone_parent.aged(at) == pytest.approx(single, abs=1e-6) + assert P.couple.aged(at) == pytest.approx(couple, abs=1e-6)