From f81447abe7f0b71f1c76580488949110646da765 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 16:25:23 -0400 Subject: [PATCH 01/10] Count IIDB, SDA and incapacity benefit in Council Tax Reduction income MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every scheme council_tax_reduction_applicable_income serves counts them: SI 2012/2885 Sch 1 para 16(1)(j), SI 2012/2886 Sch paras 39(1)(j) and 54(1), WSI 2013/3029 Sch 1 para 10(1)(j) and Sch 6 para 17(1), SSI 2012/319 reg 27(1)(j) and SSI 2021/249 reg 57(1)(b)(ix)-(xi). War pensions and AFCS payments stay uncounted: Scotland disregards or omits them, and English and Welsh councils may disregard beyond the prescribed £10 a week (WSI 2013/3029 reg 34(5); LGFA 1992 Sch 1A para 2(11), s.13A(1)(c)). Part of #2510. Co-Authored-By: Claude Opus 5.5 --- .../ctr-income-prescribed-benefits.fixed.md | 1 + ...council_tax_reduction_applicable_income.py | 48 ++++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 changelog.d/ctr-income-prescribed-benefits.fixed.md diff --git a/changelog.d/ctr-income-prescribed-benefits.fixed.md b/changelog.d/ctr-income-prescribed-benefits.fixed.md new file mode 100644 index 0000000000..6fbc0df1bb --- /dev/null +++ b/changelog.d/ctr-income-prescribed-benefits.fixed.md @@ -0,0 +1 @@ +Council Tax Reduction income now counts industrial injuries disablement benefit, severe disablement allowance and incapacity benefit in every scheme (SI 2012/2885 Sch 1 para 16; SI 2012/2886 Sch paras 39 and 54; WSI 2013/3029 Sch 1 para 10 and Sch 6 para 17; SSI 2012/319 reg 27; SSI 2021/249 reg 57). diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py index 5f09f4c4a8..66ef496f32 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py @@ -17,7 +17,12 @@ class council_tax_reduction_applicable_income(Variable): "also exclude Child Benefit from general-rule income; statutory sick " "and maternity pay remain counted as earnings. The other adjustments those " "provisions allow (childcare charges, the £20 earnings disregards, " - "maintenance disregards and the rest) are not modelled." + "maintenance disregards and the rest) are not modelled. Industrial " + "injuries disablement benefit, severe disablement allowance and " + "incapacity benefit count in every scheme. War pensions and Armed " + "Forces Compensation Scheme payments do not: Scotland disregards or " + "omits them, and English and Welsh councils may disregard them beyond " + "the prescribed £10 a week." ) definition_period = YEAR unit = GBP @@ -35,6 +40,15 @@ class council_tax_reduction_applicable_income(Variable): "https://www.legislation.gov.uk/uksi/2012/2885/schedule/4", "https://www.legislation.gov.uk/wsi/2013/3029/schedule/3", "https://www.legislation.gov.uk/ssi/2012/319/schedule/2", + "https://www.legislation.gov.uk/uksi/2012/2885/schedule/1/paragraph/16", + "https://www.legislation.gov.uk/uksi/2012/2885/schedule/5/paragraph/1", + "https://www.legislation.gov.uk/uksi/2012/2886/schedule/paragraph/39", + "https://www.legislation.gov.uk/uksi/2012/2886/schedule/paragraph/54", + "https://www.legislation.gov.uk/wsi/2013/3029/regulation/34", + "https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/10", + "https://www.legislation.gov.uk/wsi/2013/3029/schedule/6/paragraph/17", + "https://www.legislation.gov.uk/ssi/2012/319/schedule/3/paragraph/1", + "https://www.legislation.gov.uk/ukpga/1992/14/schedule/1A/paragraph/2", ] def formula(benunit, period, parameters): @@ -94,7 +108,39 @@ def formula(benunit, period, parameters): "statutory_sick_pay", "statutory_maternity_pay", "ssmg", + # Every scheme the model runs counts these three benefits. At + # pension age they are social security benefits that the income + # lists do not except: SI 2012/2885 Sch 1 para 16(1)(j), the + # default scheme SI 2012/2886 Sch para 39(1)(j), WSI 2013/3029 + # Sch 1 para 10(1)(j) and SSI 2012/319 reg 27(1)(j). At working + # age England and Wales count gross income (SI 2012/2886 Sch para + # 54(1); WSI 2013/3029 Sch 6 para 17(1)) less disregards that do + # not list them (their Schs 8 and 9), and Scotland's closed list + # names them (SSI 2021/249 reg 57(1)(b)(ix) to (xi)). Each scheme + # leaves out IIDB's constant attendance and exceptionally severe + # disablement increases (SSCBA 1992 ss.104 and 105), by name or as + # "attendance allowance" (Sch 8 and Sch 9 para 14), but iidb is + # the whole reported payment and cannot separate them. + "iidb", + "sda", + "incapacity_benefit", ] + # War disablement pensions, war widow's or widower's pensions and + # Armed Forces Compensation Scheme payments (afcs, war_widows_pension) + # are not counted. Scotland counts none of them: SSI 2012/319 Sch 3 + # para 1 disregards each in full, and SSI 2021/249 reg 57(1), a closed + # list, does not name them. England and Wales prescribe a £10 a week + # disregard (SI 2012/2885 Sch 5 para 1; SI 2012/2886 Sch 6 para 1 and + # Sch 8 para 20; WSI 2013/3029 Sch 4 para 1 and Sch 9 para 20), which + # a council may exceed: Wales makes it a minimum for war disablement + # and war widow's or widower's pensions (WSI 2013/3029 reg 34(5)), an + # English scheme may make provision equivalent to the council tax + # benefit rules (LGFA 1992 Sch 1A para 2(11)), and any council may + # reduce council tax further (s.13A(1)(c)). The model takes the full + # disregard, as Housing Benefit does for local schemes under SSAA 1992 + # s.134(8)(a). Reg 34(5) does not cover guaranteed income payments, so + # in Wales this over-disregards them, but afcs does not separate them + # from war disablement pensions. income_components = [ "employment_income", "self_employment_income", From 8fc098c160491a98aaeb0742ee90b7a436b3e904 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 17:32:31 -0400 Subject: [PATCH 02/10] =?UTF-8?q?Count=20war=20pensions=20less=20=C2=A310?= =?UTF-8?q?=20a=20week=20under=20the=20Kingston=20and=20Newham=20schemes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Kingston upon Thames (Sch 8 para 20, capped at £20 a week by para 40) and Newham (Sch 8 para 19) disregard only £10 a week of each war disablement pension, war widow's pension and AFCS guaranteed income payment for working-age applicants. Westminster, Oxford and Merton disregard them in full, and the national schemes keep the full disregard. The per-payment reading follows Pension Credit's identically worded Sch IV para 1. Part of #2510. Co-Authored-By: Claude Opus 5.5 --- .../ctr-income-prescribed-benefits.fixed.md | 2 +- .../means_test/war_pension_disregard.yaml | 10 +++ .../war_pension_disregard_limit.yaml | 10 +++ .../means_test/war_pension_disregard.yaml | 10 +++ ...council_tax_reduction_applicable_income.py | 88 ++++++++++++++----- 5 files changed, 99 insertions(+), 21 deletions(-) create mode 100644 policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml create mode 100644 policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard_limit.yaml create mode 100644 policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml diff --git a/changelog.d/ctr-income-prescribed-benefits.fixed.md b/changelog.d/ctr-income-prescribed-benefits.fixed.md index 6fbc0df1bb..8a8df3b7b2 100644 --- a/changelog.d/ctr-income-prescribed-benefits.fixed.md +++ b/changelog.d/ctr-income-prescribed-benefits.fixed.md @@ -1 +1 @@ -Council Tax Reduction income now counts industrial injuries disablement benefit, severe disablement allowance and incapacity benefit in every scheme (SI 2012/2885 Sch 1 para 16; SI 2012/2886 Sch paras 39 and 54; WSI 2013/3029 Sch 1 para 10 and Sch 6 para 17; SSI 2012/319 reg 27; SSI 2021/249 reg 57). +Council Tax Reduction income now counts industrial injuries disablement benefit, severe disablement allowance and incapacity benefit in every scheme (SI 2012/2885 Sch 1 para 16; SI 2012/2886 Sch paras 39 and 54; WSI 2013/3029 Sch 1 para 10 and Sch 6 para 17; SSI 2012/319 reg 27; SSI 2021/249 reg 57), and counts war pensions and Armed Forces Compensation Scheme payments less £10 a week each under the Kingston upon Thames and Newham working-age schemes. diff --git a/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml b/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml new file mode 100644 index 0000000000..08a5de490a --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml @@ -0,0 +1,10 @@ +description: Weekly amount of each war disablement pension, war widow's or widower's pension and Armed Forces Compensation Scheme guaranteed income payment disregarded from a working-age applicant's income under the Kingston upon Thames Council Tax Reduction scheme. +values: + 2026-04-01: 10 +metadata: + unit: currency-GBP + period: week + label: Kingston upon Thames Council Tax Reduction war pension disregard + reference: + - title: Council Tax Reduction Scheme 2026 - 2027, Sch 8 para 20 (PDF page 136) + href: https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=136 diff --git a/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard_limit.yaml b/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard_limit.yaml new file mode 100644 index 0000000000..2a50f0ff9f --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard_limit.yaml @@ -0,0 +1,10 @@ +description: Weekly limit on a working-age family's total disregards under Sch 8 paras 20 and 21 (war pensions and widowed parent's allowance) and the student income disregards, under the Kingston upon Thames Council Tax Reduction scheme. +values: + 2026-04-01: 20 +metadata: + unit: currency-GBP + period: week + label: Kingston upon Thames Council Tax Reduction war pension disregard limit + reference: + - title: Council Tax Reduction Scheme 2026 - 2027, Sch 8 para 40 (PDF page 139) + href: https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=139 diff --git a/policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml b/policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml new file mode 100644 index 0000000000..48e1dd254e --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml @@ -0,0 +1,10 @@ +description: Weekly amount of each war disablement pension, war widow's or widower's pension and Armed Forces Compensation Scheme guaranteed income payment disregarded from a working-age applicant's income under the Newham Council Tax Reduction scheme. +values: + 2026-04-01: 10 +metadata: + unit: currency-GBP + period: week + label: Newham Council Tax Reduction war pension disregard + reference: + - title: Council Tax Reduction Scheme 2026, Sch 8 para 19 (PDF page 292) + href: https://www.newham.gov.uk/downloads/file/10753/council-tax-reduction-scheme-2026#page=292 diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py index 66ef496f32..b37764428a 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py @@ -1,4 +1,8 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.local_authorities.council_tax_reduction.config import ( + is_kingston_upon_thames_working_age, + is_newham_working_age, +) from policyengine_uk.variables.household.demographic.country import Country @@ -20,9 +24,11 @@ class council_tax_reduction_applicable_income(Variable): "maintenance disregards and the rest) are not modelled. Industrial " "injuries disablement benefit, severe disablement allowance and " "incapacity benefit count in every scheme. War pensions and Armed " - "Forces Compensation Scheme payments do not: Scotland disregards or " - "omits them, and English and Welsh councils may disregard them beyond " - "the prescribed £10 a week." + "Forces Compensation Scheme payments count, less £10 a week each, " + "only under the Kingston upon Thames and Newham working-age schemes: " + "Scotland disregards or omits them, and elsewhere in England and " + "Wales councils may disregard them beyond the prescribed £10 a week, " + "so the model takes the full disregard." ) definition_period = YEAR unit = GBP @@ -40,15 +46,15 @@ class council_tax_reduction_applicable_income(Variable): "https://www.legislation.gov.uk/uksi/2012/2885/schedule/4", "https://www.legislation.gov.uk/wsi/2013/3029/schedule/3", "https://www.legislation.gov.uk/ssi/2012/319/schedule/2", - "https://www.legislation.gov.uk/uksi/2012/2885/schedule/1/paragraph/16", "https://www.legislation.gov.uk/uksi/2012/2885/schedule/5/paragraph/1", "https://www.legislation.gov.uk/uksi/2012/2886/schedule/paragraph/39", "https://www.legislation.gov.uk/uksi/2012/2886/schedule/paragraph/54", "https://www.legislation.gov.uk/wsi/2013/3029/regulation/34", - "https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/10", "https://www.legislation.gov.uk/wsi/2013/3029/schedule/6/paragraph/17", "https://www.legislation.gov.uk/ssi/2012/319/schedule/3/paragraph/1", "https://www.legislation.gov.uk/ukpga/1992/14/schedule/1A/paragraph/2", + "https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=136", + "https://www.newham.gov.uk/downloads/file/10753/council-tax-reduction-scheme-2026#page=292", ] def formula(benunit, period, parameters): @@ -126,21 +132,28 @@ def formula(benunit, period, parameters): "incapacity_benefit", ] # War disablement pensions, war widow's or widower's pensions and - # Armed Forces Compensation Scheme payments (afcs, war_widows_pension) - # are not counted. Scotland counts none of them: SSI 2012/319 Sch 3 - # para 1 disregards each in full, and SSI 2021/249 reg 57(1), a closed - # list, does not name them. England and Wales prescribe a £10 a week - # disregard (SI 2012/2885 Sch 5 para 1; SI 2012/2886 Sch 6 para 1 and - # Sch 8 para 20; WSI 2013/3029 Sch 4 para 1 and Sch 9 para 20), which - # a council may exceed: Wales makes it a minimum for war disablement - # and war widow's or widower's pensions (WSI 2013/3029 reg 34(5)), an - # English scheme may make provision equivalent to the council tax - # benefit rules (LGFA 1992 Sch 1A para 2(11)), and any council may - # reduce council tax further (s.13A(1)(c)). The model takes the full - # disregard, as Housing Benefit does for local schemes under SSAA 1992 - # s.134(8)(a). Reg 34(5) does not cover guaranteed income payments, so - # in Wales this over-disregards them, but afcs does not separate them - # from war disablement pensions. + # Armed Forces Compensation Scheme payments (afcs, war_widows_pension). + # Scotland counts none of them: SSI 2012/319 Sch 3 para 1 disregards + # each in full, and SSI 2021/249 reg 57(1), a closed list, does not + # name them. England and Wales prescribe a £10 a week disregard (SI + # 2012/2885 Sch 5 para 1; SI 2012/2886 Sch 6 para 1 and Sch 8 para 20; + # WSI 2013/3029 Sch 4 para 1 and Sch 9 para 20), which a council may + # exceed: Wales makes it a minimum for war disablement and war widow's + # or widower's pensions (WSI 2013/3029 reg 34(5)), an English scheme + # may make provision equivalent to the council tax benefit rules (LGFA + # 1992 Sch 1A para 2(11)), and any council may reduce council tax + # further (s.13A(1)(c)). Where the model cannot see the council's + # choice it takes the full disregard, as Housing Benefit does for local + # schemes under SSAA 1992 s.134(8)(a). Reg 34(5) does not cover + # guaranteed income payments, so in Wales that over-disregards them, + # but afcs does not separate them from war disablement pensions. + # Of the English working-age schemes the model runs, Westminster's, + # Oxford's and Merton's disregard war pensions in full. Kingston upon + # Thames (Sch 8 para 20) and Newham (Sch 8 para 19) disregard £10 a + # week of each payment, the reading Pension Credit's identically + # worded disregard takes, and Kingston limits the family's total to + # £20 a week (Sch 8 para 40). Newham's limit (its para 39) names its + # paras 20 and 21, not the war pension para 19, so it does not apply. income_components = [ "employment_income", "self_employment_income", @@ -174,6 +187,41 @@ def formula(benunit, period, parameters): credits = add_for_members(benunit, period, ["tax_credits"], members) increased_income = income + personal_benefit_income + credits + benefits + local_authority = benunit.household("local_authority", period) + kingston = is_kingston_upon_thames_working_age( + local_authority, country, pensioner + ) + newham = is_newham_working_age(local_authority, country, pensioner) + local_authorities = parameters(period).gov.local_authorities + kingston_test = ( + local_authorities.kingston_upon_thames.council_tax_reduction.means_test + ) + newham_test = local_authorities.newham.council_tax_reduction.means_test + payment_disregard = WEEKS_IN_YEAR * where( + kingston, + kingston_test.war_pension_disregard, + newham_test.war_pension_disregard, + ) + war_pensions = 0 + war_pension_disregards = 0 + for payment in ["afcs", "war_widows_pension"]: + amount = person(payment, period) * members + war_pensions += benunit.sum(amount) + war_pension_disregards += benunit.sum( + min_(amount, benunit.project(payment_disregard)) + ) + war_pension_disregards = where( + kingston, + min_( + war_pension_disregards, + kingston_test.war_pension_disregard_limit * WEEKS_IN_YEAR, + ), + war_pension_disregards, + ) + increased_income += where( + kingston | newham, war_pensions - war_pension_disregards, 0 + ) + if not bi.interactions.include_in_means_tests: increased_income -= add_for_members( benunit, period, ["basic_income"], members From b26748bd63528073454e7d1015b5e5f90e5aad8e Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 17:57:55 -0400 Subject: [PATCH 03/10] Test IIDB, SDA, IB and war pensions in Council Tax Reduction income MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit YAML cases per scheme at the 2026-27 statutory rates (SI 2026/148), and Hypothesis properties: Council Tax Reduction and Pension Credit assess the same pension-age income; each benefit counts pound for pound in every scheme; war pensions count only under the Kingston upon Thames and Newham working-age schemes, less £10 a week each. Part of #2510. Co-Authored-By: Claude Opus 5.5 --- ...cil_tax_reduction_prescribed_benefits.yaml | 540 ++++++++++++++++++ ...eduction_prescribed_benefits_properties.py | 418 ++++++++++++++ 2 files changed, 958 insertions(+) create mode 100644 policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml create mode 100644 policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml new file mode 100644 index 0000000000..cdb1658fb6 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml @@ -0,0 +1,540 @@ +# Industrial injuries disablement benefit (IIDB), severe disablement allowance +# (SDA), incapacity benefit (IB) and war pensions in Council Tax Reduction +# income. +# +# Every scheme counts IIDB, SDA and IB: +# - England, pensioners: SI 2012/2885 Sch 1 para 16(1)(j), social security +# benefits other than those listed; (j)(v) excepts only IIDB's increases +# under SSCBA 1992 ss.104 and 105. +# - English working-age schemes: gross income (SI 2012/2886 Sch para 54(1)) +# less Sch 8, which disregards none of the three (para 14 disregards +# "attendance allowance", defined to include the ss.104 and 105 increases). +# Kingston upon Thames, Merton and Newham copy the default scheme; Oxford's +# para 24 counts income other than earnings unless its Appendix 5 lists it. +# - Wales: WSI 2013/3029 Sch 1 para 10(1)(j) and Sch 6 para 17(1) with Sch 9. +# - Scotland: SSI 2012/319 reg 27(1)(j) (pension age) and SSI 2021/249 reg +# 57(1)(b)(ix) to (xi) (working age, a closed list). +# +# War pensions and AFCS payments (afcs, war_widows_pension): +# - Scotland counts none of them: SSI 2012/319 reg 31(10) and Sch 3 para 1 +# disregard each payment in full, and SSI 2021/249 reg 57(1) does not name +# them. +# - England and Wales prescribe a £10 a week disregard (SI 2012/2885 Sch 5 +# para 1; WSI 2013/3029 Sch 4 para 1 and Sch 9 para 20) that councils may +# exceed (WSI 2013/3029 reg 34(5); LGFA 1992 Sch 1A para 2(11) and +# s.13A(1)(c)). The model takes the full disregard where it cannot see the +# council's choice. +# - Of the English working-age schemes the model runs, Westminster's, Oxford's +# and Merton's disregard war pensions in full. Kingston upon Thames (Sch 8 +# para 20) and Newham (Sch 8 para 19) disregard £10 a week of each payment; +# Kingston limits the family's total to £20 a week (Sch 8 para 40). +# +# Weekly rates, 2026-27 (SI 2026/148 Sch 1, restating SSCBA 1992 Sch 4): +# disablement pension at 100% 233.90, 50% 116.95, 20% 46.78; long-term +# incapacity benefit 146.60; severe disablement allowance 103.85 plus the +# higher age related addition 15.50, 119.35 in all. Full new State Pension +# 241.30 (SI 2026/148 art 6(1)), 12,547.60 a year, below the 12,570 personal +# allowance. IIDB, SDA and war pensions are not taxed in the model; State +# Pension and IB are. Pension Credit standard minimum guarantee 238.00 +# (single). CTR applicable amounts: personal allowance 256.00 (single, pension +# age) and 95.55 (single, 25 or over); the reduction is withdrawn at 20% of +# income above the applicable amount. Council tax is 2,000 throughout. + +- name: IIDB counts as pension-age Council Tax Reduction income in England + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + state_pension: 241.30 * 52 + iidb_reported: 116.95 * 52 + benunits: + benunit: + members: [pensioner] + would_claim_pc: false + households: + household: + members: [pensioner] + country: ENGLAND + region: SOUTH_EAST + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_pensioner: true + income_tax: 0 + # 12,547.60 + 6,081.40 + council_tax_reduction_applicable_income: 241.30 * 52 + 116.95 * 52 + council_tax_reduction_applicable_amount: 256.00 * 52 + # 2,000 - 0.2 x (18,629.00 - 13,312.00) = 936.60 + council_tax_benefit: 2_000 - 0.2 * ((241.30 + 116.95 - 256.00) * 52) + +- name: IIDB counts as pension-age Council Tax Reduction income in Wales + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + state_pension: 241.30 * 52 + iidb_reported: 233.90 * 52 + benunits: + benunit: + members: [pensioner] + would_claim_pc: false + households: + household: + members: [pensioner] + country: WALES + region: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + income_tax: 0 + # 12,547.60 + 12,162.80 + council_tax_reduction_applicable_income: 241.30 * 52 + 233.90 * 52 + +- name: IIDB counts as pension-age Council Tax Reduction income in Scotland + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + state_pension: 241.30 * 52 + iidb_reported: 46.78 * 52 + benunits: + benunit: + members: [pensioner] + would_claim_pc: false + households: + household: + members: [pensioner] + country: SCOTLAND + region: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + income_tax: 0 + # 12,547.60 + 2,432.56 + council_tax_reduction_applicable_income: 241.30 * 52 + 46.78 * 52 + council_tax_reduction_applicable_amount: 256.00 * 52 + # 2,000 - 0.2 x (14,980.16 - 13,312.00) = 1,666.37 + council_tax_benefit: 2_000 - 0.2 * ((241.30 + 46.78 - 256.00) * 52) + +- name: SDA counts as working-age Council Tax Reduction income under the Westminster scheme + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 55 + sda_reported: 1 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + households: + household: + members: [claimant] + country: ENGLAND + region: LONDON + local_authority: WESTMINSTER + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_pensioner: false + # 103.85 + 15.50 + sda: 119.35 * 52 + council_tax_reduction_applicable_income: 119.35 * 52 + +- name: IB counts as working-age Council Tax Reduction income in Wales + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 55 + incapacity_benefit_reported: 146.60 * 52 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + households: + household: + members: [claimant] + country: WALES + region: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + # 7,623.20 is below the personal allowance. + income_tax: 0 + council_tax_reduction_applicable_income: 146.60 * 52 + +- name: IIDB and IB count as working-age Council Tax Reduction income in Scotland + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 50 + iidb_reported: 46.78 * 52 + incapacity_benefit_reported: 146.60 * 52 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + households: + household: + members: [claimant] + country: SCOTLAND + region: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_pensioner: false + income_tax: 0 + # 2,432.56 + 7,623.20 + council_tax_reduction_applicable_income: (46.78 + 146.60) * 52 + council_tax_reduction_applicable_amount: 95.55 * 52 + # 2,000 - 0.2 x (10,055.76 - 4,968.60) = 982.57 + council_tax_benefit: 2_000 - 0.2 * ((46.78 + 146.60 - 95.55) * 52) + +- name: Taxable IB counts net of income tax under the Westminster scheme + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 55 + incapacity_benefit_reported: 146.60 * 52 + private_pension_income: 10_000 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + households: + household: + members: [claimant] + country: ENGLAND + region: LONDON + local_authority: WESTMINSTER + council_tax: 2_000 + savings: 0 + output: + # 0.2 x (7,623.20 + 10,000 - 12,570) = 1,010.64 + income_tax: 0.2 * (146.60 * 52 + 10_000 - 12_570) + # 17,623.20 - 1,010.64 = 16,612.56 + council_tax_reduction_applicable_income: 146.60 * 52 + 10_000 - 0.2 * (146.60 * 52 + 10_000 - 12_570) + +- name: War pensions do not count as pension-age Council Tax Reduction income in England + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 75 + state_pension: 241.30 * 52 + afcs_reported: 5_000 + war_widows_pension_reported: 3_000 + benunits: + benunit: + members: [pensioner] + would_claim_pc: false + households: + household: + members: [pensioner] + country: ENGLAND + region: SOUTH_EAST + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + income_tax: 0 + council_tax_reduction_applicable_income: 241.30 * 52 + +- name: War pensions do not count as working-age Council Tax Reduction income in Wales + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + afcs_reported: 5_000 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + households: + household: + members: [claimant] + country: WALES + region: WALES + local_authority: CARDIFF + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_applicable_income: 0 + +- name: War pensions do not count as pension-age Council Tax Reduction income in Scotland + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 80 + state_pension: 241.30 * 52 + war_widows_pension_reported: 3_000 + benunits: + benunit: + members: [pensioner] + would_claim_pc: false + households: + household: + members: [pensioner] + country: SCOTLAND + region: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_applicable_income: 241.30 * 52 + +- name: War pensions do not count as working-age Council Tax Reduction income in Scotland + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 45 + afcs_reported: 5_000 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + households: + household: + members: [claimant] + country: SCOTLAND + region: SCOTLAND + local_authority: GLASGOW_CITY + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_applicable_income: 0 + +- name: The Kingston upon Thames working-age scheme counts a war pension less 10 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + afcs_reported: 5_000 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + households: + household: + members: [claimant] + country: ENGLAND + region: LONDON + local_authority: KINGSTON_UPON_THAMES + council_tax: 2_000 + savings: 0 + output: + # 5,000 - 520 + council_tax_reduction_applicable_income: 5_000 - 10 * 52 + +- name: The Kingston upon Thames scheme disregards 10 a week of each war pension + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + afcs_reported: 5_000 + war_widows_pension_reported: 3_000 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + households: + household: + members: [claimant] + country: ENGLAND + region: LONDON + local_authority: KINGSTON_UPON_THAMES + council_tax: 2_000 + savings: 0 + output: + # 8,000 - 2 x 520: two payments, two disregards, within the 20 a week limit. + council_tax_reduction_applicable_income: 8_000 - 2 * 10 * 52 + +- name: The Kingston upon Thames scheme limits a family's war pension disregards to 20 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + afcs_reported: 5_000 + war_widows_pension_reported: 3_000 + partner: + age: 40 + afcs_reported: 5_000 + war_widows_pension_reported: 3_000 + benunits: + benunit: + members: [claimant, partner] + would_claim_uc: false + households: + household: + members: [claimant, partner] + country: ENGLAND + region: LONDON + local_authority: KINGSTON_UPON_THAMES + council_tax: 2_000 + savings: 0 + output: + # Four payments would give 4 x 10 a week; Sch 8 para 40 limits the + # family's total to 20 a week: 16,000 - 1,040. + council_tax_reduction_applicable_income: 16_000 - 20 * 52 + +- name: The Newham working-age scheme disregards 10 a week of each war pension, with no family limit + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + afcs_reported: 5_000 + war_widows_pension_reported: 3_000 + partner: + age: 40 + afcs_reported: 5_000 + war_widows_pension_reported: 3_000 + benunits: + benunit: + members: [claimant, partner] + would_claim_uc: false + households: + household: + members: [claimant, partner] + country: ENGLAND + region: LONDON + local_authority: NEWHAM + council_tax: 2_000 + savings: 0 + output: + # Newham's limit (its Sch 8 para 39) names paras 20 and 21, not the war + # pension para 19: 16,000 - 4 x 520. + council_tax_reduction_applicable_income: 16_000 - 4 * 10 * 52 + +- name: A war pension below 10 a week counts as nothing under the Newham scheme + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + afcs_reported: 300 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + households: + household: + members: [claimant] + country: ENGLAND + region: LONDON + local_authority: NEWHAM + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_applicable_income: 0 + +- name: The Westminster working-age scheme disregards war pensions in full + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 40 + afcs_reported: 5_000 + war_widows_pension_reported: 3_000 + benunits: + benunit: + members: [claimant] + would_claim_uc: false + households: + household: + members: [claimant] + country: ENGLAND + region: LONDON + local_authority: WESTMINSTER + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_applicable_income: 0 + +- name: The national pensioner scheme takes the full war pension disregard in Kingston upon Thames + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + afcs_reported: 5_000 + benunits: + benunit: + members: [pensioner] + would_claim_pc: false + households: + household: + members: [pensioner] + country: ENGLAND + region: LONDON + local_authority: KINGSTON_UPON_THAMES + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_pensioner: true + council_tax_reduction_applicable_income: 0 + +- name: A guarantee credit recipient with IIDB has nil Council Tax Reduction income + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + state_pension: 150 * 52 + iidb_reported: 46.78 * 52 + benunits: + benunit: + members: [pensioner] + would_claim_pc: true + households: + household: + members: [pensioner] + country: ENGLAND + region: SOUTH_EAST + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 + output: + # 7,800 + 2,432.56 = 10,232.56, or 196.78 a week, below the 238.00 + # minimum guarantee (SPC Regs 2002 reg 15(1) counts IIDB). + pension_credit_income: (150 + 46.78) * 52 + in_receipt_of_guarantee_credit: true + # SI 2012/2885 Sch 1 para 13: the whole income is disregarded. + council_tax_reduction_applicable_income: 0 + council_tax_benefit: 2_000 diff --git a/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py b/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py new file mode 100644 index 0000000000..31ea275684 --- /dev/null +++ b/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py @@ -0,0 +1,418 @@ +"""IIDB, SDA, incapacity benefit and war pensions in Council Tax Reduction income. + +Council Tax Reduction income left out industrial injuries disablement benefit +(IIDB), severe disablement allowance (SDA) and incapacity benefit (IB), which +every scheme the model runs counts: + +- England, pensioners: SI 2012/2885 Sch 1 para 16(1)(j), social security + benefits other than those it lists; (j)(v) excepts only IIDB's increases + under SSCBA 1992 ss.104 and 105. +- English working-age schemes: gross income (SI 2012/2886 Sch para 54(1)) + less Sch 8, which disregards none of the three; Kingston upon Thames, Merton + and Newham copy it, and Oxford counts income other than earnings unless its + Appendix 5 lists it. +- Wales: WSI 2013/3029 Sch 1 para 10(1)(j) and Sch 6 para 17(1) with Sch 9. +- Scotland: SSI 2012/319 reg 27(1)(j) and SSI 2021/249 reg 57(1)(b)(ix)-(xi). + +Pension Credit counts them too (SPC Regs 2002 reg 15(1)), so once the +guarantee credit passport ends, Council Tax Reduction was assessed on less +income than Pension Credit and Housing Benefit. + +War pensions and Armed Forces Compensation Scheme payments stay uncounted +where the model cannot see the council's choice: Scotland disregards or omits +them (SSI 2012/319 Sch 3 para 1; SSI 2021/249 reg 57(1)), and English and +Welsh councils may disregard more than the prescribed £10 a week (WSI 2013/3029 +reg 34(5); LGFA 1992 Sch 1A para 2(11) and s.13A(1)(c)). Kingston upon Thames +(Sch 8 para 20) and Newham (Sch 8 para 19) disregard only £10 a week of each +payment at working age, and Kingston limits the family's total to £20 a week +(Sch 8 para 40). Westminster, Merton and Oxford disregard them in full. + +Invariants, in 2026, for single people and couples with no children: + +1. Differential: Council Tax Reduction and Pension Credit assess the same + income. For pension-age families under the national pensioner schemes in + England, Wales and Scotland, with State Pension, private pension, IIDB, IB + and SDA, no earnings, no pension contributions and savings of at most + £10,000 (so neither scheme's deemed income from capital applies): + Council Tax Reduction income is nil where guarantee credit is received + (SI 2012/2885 Sch 1 para 13 and equivalents), Pension Credit income plus + the savings credit where the award is savings credit only (para 14), and + Pension Credit income otherwise. +2. Pound for pound, every scheme: raising IIDB or IB by d, or awarding SDA, + raises Council Tax Reduction income plus the family's income tax and + National Insurance by exactly the added amount, wherever the general rules + apply before and after and the zero floor does not bind. The reduction + never rises where the route (general rules, savings credit only, guarantee + credit) is unchanged; in the national schemes, where both awards are + strictly between nil and the liability, it falls by 20% of the rise in + income. +3. War pensions: adding war pensions leaves Council Tax Reduction income + unchanged under the general rules, except under the Kingston upon Thames + and Newham working-age schemes, where it rises by the payments less £10 a + week of each (in Kingston, less at most £20 a week in all). The disregards + are written out here from the schemes, not read from the parameters. + +These compare the model's own measures. Invariant 1 relies on both measures +deducting the family's whole income tax and National Insurance, an existing +approximation in each. +""" + +import numpy as np +from hypothesis import HealthCheck, example, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +YEAR = 2026 +WEEKS = 52 +# 2026-27 weekly rates (SI 2026/148 Sch 1). +IIDB_FULL = 233.90 * WEEKS +LONG_TERM_IB = 146.60 * WEEKS +SDA = (103.85 + 15.50) * WEEKS +WITHDRAWAL_RATE = 0.2 +# £10 a week of each war pension; Kingston's £20 a week family limit. +LOCAL_WAR_PENSION_DISREGARD = 10 * WEEKS +KINGSTON_DISREGARD_LIMIT = 20 * WEEKS +PRIVATE_PENSIONS = np.arange(0, 20_001, 1_000) +PROPERTY_SETTINGS = settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +# Areas of the national pensioner schemes, and of the working-age schemes. +PENSION_AGE_AREAS = { + "MAIDSTONE": ("ENGLAND", "SOUTH_EAST"), + "KINGSTON_UPON_THAMES": ("ENGLAND", "LONDON"), + "CARDIFF": ("WALES", "WALES"), + "GLASGOW_CITY": ("SCOTLAND", "SCOTLAND"), +} +WORKING_AGE_AREAS = { + "WESTMINSTER": ("ENGLAND", "LONDON"), + "MERTON": ("ENGLAND", "LONDON"), + "KINGSTON_UPON_THAMES": ("ENGLAND", "LONDON"), + "NEWHAM": ("ENGLAND", "LONDON"), + "OXFORD": ("ENGLAND", "SOUTH_EAST"), + "CARDIFF": ("WALES", "WALES"), + "GLASGOW_CITY": ("SCOTLAND", "SCOTLAND"), +} +NATIONAL_SCHEME_AREAS = {"MAIDSTONE", "CARDIFF", "GLASGOW_CITY"} +LOCAL_WAR_PENSION_AREAS = {"KINGSTON_UPON_THAMES", "NEWHAM"} +BENUNIT_VARIABLES = [ + "council_tax_reduction_applicable_income", + "council_tax_benefit", + "council_tax_reduction_pensioner", + "council_tax_reduction_relevant_income_based_benefit", + "in_receipt_of_guarantee_credit", + "in_receipt_of_savings_credit_only", + "pension_credit_income", + "pension_credit", +] +PERSON_TOTALS = ["income_tax", "national_insurance"] + + +def money(high): + return st.floats(0, high, allow_nan=False, allow_infinity=False) + + +@st.composite +def benefits(draw): + """IIDB at a 20% to 100% assessment, IB up to the long-term rate, SDA.""" + return dict( + iidb=draw(st.one_of(st.just(0.0), money(IIDB_FULL))), + incapacity_benefit=draw(st.one_of(st.just(0.0), money(LONG_TERM_IB))), + sda=draw(st.booleans()), + ) + + +@st.composite +def pension_age_family(draw, areas=tuple(PENSION_AGE_AREAS)): + adults = draw(st.integers(1, 2)) + return dict( + ages=[draw(st.integers(67, 95)) for _ in range(adults)], + state_pensions=[draw(money(15_000)) for _ in range(adults)], + earnings=[0.0] * adults, + benefits=[draw(benefits()) for _ in range(adults)], + property_income=draw(st.one_of(st.just(0.0), money(5_000))), + area=draw(st.sampled_from(areas)), + council_tax=draw(st.floats(500, 4_000)), + savings=draw(st.one_of(st.just(0.0), money(10_000))), + would_claim_pc=draw(st.booleans()), + ) + + +@st.composite +def working_age_family(draw): + adults = draw(st.integers(1, 2)) + return dict( + ages=[draw(st.integers(25, 60)) for _ in range(adults)], + state_pensions=[0.0] * adults, + earnings=[draw(st.one_of(st.just(0.0), money(30_000))) for _ in range(adults)], + benefits=[draw(benefits()) for _ in range(adults)], + property_income=draw(st.one_of(st.just(0.0), money(5_000))), + area=draw(st.sampled_from(tuple(WORKING_AGE_AREAS))), + council_tax=draw(st.floats(500, 4_000)), + savings=draw(st.one_of(st.just(0.0), money(5_000))), + would_claim_pc=False, + ) + + +any_family = st.one_of(pension_age_family(), working_age_family()) + +# A single pensioner off Pension Credit with every benefit and State Pension +# below the personal allowance, so the general rules apply in every cell. +GENERAL_RULES_PENSIONER = dict( + ages=[75], + state_pensions=[9_000.0], + earnings=[0.0], + benefits=[dict(iidb=IIDB_FULL * 0.5, incapacity_benefit=LONG_TERM_IB, sda=True)], + property_income=0.0, + area="MAIDSTONE", + council_tax=2_000.0, + savings=0.0, + would_claim_pc=False, +) +# A pensioner whose reduction is partial before and after each change, so the +# 20% taper in invariant 2 is checked. +PARTIAL_AWARD_PENSIONER = dict( + ages=[70], + state_pensions=[241.30 * WEEKS], + earnings=[0.0], + benefits=[dict(iidb=0.0, incapacity_benefit=0.0, sda=False)], + property_income=0.0, + area="MAIDSTONE", + council_tax=2_000.0, + savings=0.0, + would_claim_pc=False, +) +# Two adults on the Kingston upon Thames working-age scheme, so the £20 limit +# binds once each has two war pensions. +KINGSTON_COUPLE = dict( + ages=[40, 40], + state_pensions=[0.0, 0.0], + earnings=[8_000.0, 0.0], + benefits=[dict(iidb=IIDB_FULL * 0.3, incapacity_benefit=0.0, sda=False)] * 2, + property_income=0.0, + area="KINGSTON_UPON_THAMES", + council_tax=2_000.0, + savings=0.0, + would_claim_pc=False, +) + + +def area_of(fam): + if fam["ages"][0] >= 67: + return PENSION_AGE_AREAS[fam["area"]] + return WORKING_AGE_AREAS[fam["area"]] + + +def situation(variants): + """One benefit unit and household per (family, private pension, change). + + variants: list of (family, private pension of the first adult, extra + person inputs for the first adult). + """ + people, benunits, households = {}, {}, {} + for i, (fam, private_pension, extra) in enumerate(variants): + country, region = area_of(fam) + names = [] + for j, age in enumerate(fam["ages"]): + name = f"p{i}_{j}" + b = fam["benefits"][j] + person = { + "age": {YEAR: age}, + "state_pension": {YEAR: fam["state_pensions"][j]}, + "employment_income": {YEAR: fam["earnings"][j]}, + "private_pension_income": {YEAR: private_pension * (j == 0)}, + "property_income": {YEAR: fam["property_income"] * (j == 0)}, + "iidb_reported": {YEAR: b["iidb"]}, + "incapacity_benefit_reported": {YEAR: b["incapacity_benefit"]}, + "sda_reported": {YEAR: float(b["sda"])}, + } + for variable, value in extra.get(j, {}).items(): + person[variable] = {YEAR: value} + people[name] = person + names.append(name) + benunits[f"b{i}"] = { + "members": names, + "would_claim_pc": {YEAR: fam["would_claim_pc"]}, + "would_claim_uc": {YEAR: False}, + } + households[f"h{i}"] = { + "members": names, + "country": {YEAR: country}, + "region": {YEAR: region}, + "local_authority": {YEAR: fam["area"]}, + "tenure_type": {YEAR: "OWNED_OUTRIGHT"}, + "council_tax": {YEAR: fam["council_tax"]}, + "savings": {YEAR: fam["savings"]}, + } + return {"people": people, "benunits": benunits, "households": households} + + +def calculate(variants): + simulation = Simulation(situation=situation(variants)) + values = {v: np.asarray(simulation.calculate(v, YEAR)) for v in BENUNIT_VARIABLES} + values["tax"] = sum( + np.asarray(simulation.calculate(v, YEAR, map_to="benunit")) + for v in PERSON_TOTALS + ) + values["liability"] = np.asarray( + simulation.calculate("council_tax_reduction_maximum_eligible_liability", YEAR) + ) + return values + + +def route(values): + """0: general rules; 1: savings credit only; 2: guarantee credit.""" + gc = values["in_receipt_of_guarantee_credit"].astype(bool) + sc_only = values["in_receipt_of_savings_credit_only"].astype(bool) + return np.where(gc, 2, np.where(sc_only, 1, 0)) + + +@PROPERTY_SETTINGS +@given(st.lists(pension_age_family(), min_size=1, max_size=4)) +@example([GENERAL_RULES_PENSIONER]) +def test_council_tax_reduction_and_pension_credit_assess_the_same_income( + families, +): + variants = [(fam, p, {}) for fam in families for p in PRIVATE_PENSIONS] + v = calculate(variants) + pensioner = v["council_tax_reduction_pensioner"].astype(bool) + assert np.all(pensioner) + ctr_income = v["council_tax_reduction_applicable_income"] + pc_income = v["pension_credit_income"] + r = route(v) + expected = np.select( + [r == 2, r == 1], [0.0, pc_income + v["pension_credit"]], default=pc_income + ) + assert np.allclose(ctr_income, expected, atol=0.01), [ + (variants[k][0], variants[k][1], ctr_income[k], expected[k], r[k]) + for k in np.flatnonzero(~np.isclose(ctr_income, expected, atol=0.01))[:3] + ] + if families == [GENERAL_RULES_PENSIONER]: + # Not vacuous: the general rules apply and count every benefit. + assert np.all(r == 0) + b = GENERAL_RULES_PENSIONER["benefits"][0] + counted = 9_000 + b["iidb"] + b["incapacity_benefit"] + SDA + assert np.isclose(ctr_income[0], counted - v["tax"][0], atol=0.01) + + +CHANGES = { + "iidb": lambda b: {"iidb_reported": b["iidb"] + 0.2 * IIDB_FULL}, + "incapacity_benefit": lambda b: { + "incapacity_benefit_reported": b["incapacity_benefit"] + 0.3 * LONG_TERM_IB + }, + "sda": lambda b: {"sda_reported": 1.0}, +} +ADDED = { + "iidb": lambda b: 0.2 * IIDB_FULL, + "incapacity_benefit": lambda b: 0.3 * LONG_TERM_IB, + "sda": lambda b: 0.0 if b["sda"] else SDA, +} + + +@PROPERTY_SETTINGS +@given( + st.lists(any_family, min_size=1, max_size=6), + st.sampled_from(sorted(CHANGES)), +) +@example( + [GENERAL_RULES_PENSIONER, KINGSTON_COUPLE, PARTIAL_AWARD_PENSIONER], + "incapacity_benefit", +) +@example([PARTIAL_AWARD_PENSIONER], "iidb") +def test_each_benefit_counts_pound_for_pound_in_every_scheme(families, change): + variants = [] + for fam in families: + b = fam["benefits"][0] + variants.append((fam, 5_000.0, {})) + variants.append((fam, 5_000.0, {0: CHANGES[change](b)})) + v = calculate(variants) + base, plus = slice(0, None, 2), slice(1, None, 2) + added = np.array([ADDED[change](fam["benefits"][0]) for fam in families]) + income = v["council_tax_reduction_applicable_income"] + with_tax = income + v["tax"] + r = route(v) + passported = v["council_tax_reduction_relevant_income_based_benefit"].astype(bool) + general = (r[base] == 0) & (r[plus] == 0) & ~passported[base] & ~passported[plus] + unfloored = general & (income[base] > 0) + assert np.allclose( + (with_tax[plus] - with_tax[base])[unfloored], added[unfloored], atol=0.01 + ), (change, [f for f, u in zip(families, unfloored) if u]) + ctr = v["council_tax_benefit"] + same_route = r[base] == r[plus] + assert np.all((ctr[plus] <= ctr[base] + 0.01)[same_route]), change + national = np.array([fam["area"] in NATIONAL_SCHEME_AREAS for fam in families]) + liability = v["liability"] + partial = ( + general + & national + & (ctr[base] > 0.01) + & (ctr[plus] > 0.01) + & (ctr[base] < liability[base] - 0.01) + & (ctr[plus] < liability[plus] - 0.01) + ) + assert np.allclose( + (ctr[base] - ctr[plus])[partial], + (WITHDRAWAL_RATE * (income[plus] - income[base]))[partial], + atol=0.01, + ), change + if PARTIAL_AWARD_PENSIONER in families: + assert partial[families.index(PARTIAL_AWARD_PENSIONER)] + + +@st.composite +def war_pensions(draw): + return { + "afcs_reported": draw(money(15_000)), + "war_widows_pension_reported": draw(st.one_of(st.just(0.0), money(8_000))), + } + + +def local_war_pension_income(payments, area): + """Counted war pensions under Kingston's Sch 8 para 20 and 40 or + Newham's Sch 8 para 19, from each adult's payments.""" + amounts = [x for person in payments for x in person.values()] + disregard = sum(min(x, LOCAL_WAR_PENSION_DISREGARD) for x in amounts) + if area == "KINGSTON_UPON_THAMES": + disregard = min(disregard, KINGSTON_DISREGARD_LIMIT) + return sum(amounts) - disregard + + +@PROPERTY_SETTINGS +@given(st.data()) +def test_war_pensions_count_only_where_the_scheme_says(data): + # Every draw ends with the Kingston couple, whose four payments meet the + # £20 a week limit, so the local branch is always exercised. + families = data.draw(st.lists(any_family, min_size=1, max_size=6)) + families = families + [KINGSTON_COUPLE] + payments = [[data.draw(war_pensions()) for _ in fam["ages"]] for fam in families] + payments[-1] = [ + {"afcs_reported": 5_000.0, "war_widows_pension_reported": 3_000.0} + ] * 2 + variants = [] + for fam, pay in zip(families, payments): + variants.append((fam, 5_000.0, {})) + variants.append((fam, 5_000.0, dict(enumerate(pay)))) + v = calculate(variants) + base, plus = slice(0, None, 2), slice(1, None, 2) + income = v["council_tax_reduction_applicable_income"] + r = route(v) + pensioner = v["council_tax_reduction_pensioner"].astype(bool) + general = (r[base] == 0) & (r[plus] == 0) & (income[base] > 0) + expected = np.array( + [ + local_war_pension_income(pay, fam["area"]) + if fam["area"] in LOCAL_WAR_PENSION_AREAS and not pensioner[2 * k] + else 0.0 + for k, (fam, pay) in enumerate(zip(families, payments)) + ] + ) + rise = income[plus] - income[base] + assert np.allclose(rise[general], expected[general], atol=0.01), [ + (families[k]["area"], payments[k], rise[k], expected[k]) + for k in np.flatnonzero(general & ~np.isclose(rise, expected, atol=0.01))[:3] + ] + # The Kingston couple's four payments meet the £20 a week limit. + assert np.isclose(rise[-1], 16_000 - KINGSTON_DISREGARD_LIMIT, atol=0.01) From f0273113faebf9635ca458fd7d154784901ab637 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 00:17:36 -0400 Subject: [PATCH 04/10] Apply the Kingston and Newham war pension disregard at pension age too MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each council's pensioner schedule (Sch 6 para 1) disregards only £10 a week of each war pension, with no family limit; Kingston's £20 a week limit (Sch 8 para 40) is a working-age rule. Elsewhere the national pensioner scheme keeps the full disregard. Part of #2510. Co-Authored-By: Claude Opus 5.5 --- .../ctr-income-prescribed-benefits.fixed.md | 2 +- .../means_test/war_pension_disregard.yaml | 6 +- .../means_test/war_pension_disregard.yaml | 6 +- ...cil_tax_reduction_prescribed_benefits.yaml | 68 +++++++++++++++++-- ...eduction_prescribed_benefits_properties.py | 31 +++++---- ...council_tax_reduction_applicable_income.py | 38 ++++++----- 6 files changed, 110 insertions(+), 41 deletions(-) diff --git a/changelog.d/ctr-income-prescribed-benefits.fixed.md b/changelog.d/ctr-income-prescribed-benefits.fixed.md index 8a8df3b7b2..3915edbe55 100644 --- a/changelog.d/ctr-income-prescribed-benefits.fixed.md +++ b/changelog.d/ctr-income-prescribed-benefits.fixed.md @@ -1 +1 @@ -Council Tax Reduction income now counts industrial injuries disablement benefit, severe disablement allowance and incapacity benefit in every scheme (SI 2012/2885 Sch 1 para 16; SI 2012/2886 Sch paras 39 and 54; WSI 2013/3029 Sch 1 para 10 and Sch 6 para 17; SSI 2012/319 reg 27; SSI 2021/249 reg 57), and counts war pensions and Armed Forces Compensation Scheme payments less £10 a week each under the Kingston upon Thames and Newham working-age schemes. +Council Tax Reduction income now counts industrial injuries disablement benefit, severe disablement allowance and incapacity benefit in every scheme (SI 2012/2885 Sch 1 para 16; SI 2012/2886 Sch paras 39 and 54; WSI 2013/3029 Sch 1 para 10 and Sch 6 para 17; SSI 2012/319 reg 27; SSI 2021/249 reg 57), and counts war pensions and Armed Forces Compensation Scheme payments less £10 a week each under the Kingston upon Thames and Newham schemes. diff --git a/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml b/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml index 08a5de490a..4601fb6c5b 100644 --- a/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml +++ b/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml @@ -1,4 +1,4 @@ -description: Weekly amount of each war disablement pension, war widow's or widower's pension and Armed Forces Compensation Scheme guaranteed income payment disregarded from a working-age applicant's income under the Kingston upon Thames Council Tax Reduction scheme. +description: Weekly amount of each war disablement pension, war widow's or widower's pension and Armed Forces Compensation Scheme guaranteed income payment disregarded from an applicant's income under the Kingston upon Thames Council Tax Reduction scheme, at working age and at pension age. values: 2026-04-01: 10 metadata: @@ -6,5 +6,7 @@ metadata: period: week label: Kingston upon Thames Council Tax Reduction war pension disregard reference: - - title: Council Tax Reduction Scheme 2026 - 2027, Sch 8 para 20 (PDF page 136) + - title: Council Tax Reduction Scheme 2026 - 2027, Sch 8 para 20 (persons who are not pensioners, PDF page 136) href: https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=136 + - title: Council Tax Reduction Scheme 2026 - 2027, Sch 6 para 1 (pensioners, PDF page 127) + href: https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=127 diff --git a/policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml b/policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml index 48e1dd254e..966221539d 100644 --- a/policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml +++ b/policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml @@ -1,4 +1,4 @@ -description: Weekly amount of each war disablement pension, war widow's or widower's pension and Armed Forces Compensation Scheme guaranteed income payment disregarded from a working-age applicant's income under the Newham Council Tax Reduction scheme. +description: Weekly amount of each war disablement pension, war widow's or widower's pension and Armed Forces Compensation Scheme guaranteed income payment disregarded from an applicant's income under the Newham Council Tax Reduction scheme, at working age and at pension age. values: 2026-04-01: 10 metadata: @@ -6,5 +6,7 @@ metadata: period: week label: Newham Council Tax Reduction war pension disregard reference: - - title: Council Tax Reduction Scheme 2026, Sch 8 para 19 (PDF page 292) + - title: Council Tax Reduction Scheme 2026, Sch 8 para 19 (persons who are not pensioners, PDF page 292) href: https://www.newham.gov.uk/downloads/file/10753/council-tax-reduction-scheme-2026#page=292 + - title: Council Tax Reduction Scheme 2026, Sch 6 para 1 (pensioners, PDF page 273) + href: https://www.newham.gov.uk/downloads/file/10753/council-tax-reduction-scheme-2026#page=273 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml index cdb1658fb6..ce66ea45fd 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml @@ -24,10 +24,12 @@ # exceed (WSI 2013/3029 reg 34(5); LGFA 1992 Sch 1A para 2(11) and # s.13A(1)(c)). The model takes the full disregard where it cannot see the # council's choice. -# - Of the English working-age schemes the model runs, Westminster's, Oxford's -# and Merton's disregard war pensions in full. Kingston upon Thames (Sch 8 -# para 20) and Newham (Sch 8 para 19) disregard £10 a week of each payment; -# Kingston limits the family's total to £20 a week (Sch 8 para 40). +# - Of the councils whose schemes the model reads, Westminster's, Oxford's and +# Merton's working-age schemes disregard war pensions in full. Kingston upon +# Thames and Newham disregard £10 a week of each payment at working age +# (Kingston Sch 8 para 20, Newham Sch 8 para 19) and at pension age (each +# council's Sch 6 para 1). Kingston limits a working-age family's total to +# £20 a week (Sch 8 para 40); neither pensioner schedule has a limit. # # Weekly rates, 2026-27 (SI 2026/148 Sch 1, restating SSCBA 1992 Sch 4): # disablement pension at 100% 233.90, 50% 116.95, 20% 46.78; long-term @@ -485,7 +487,7 @@ output: council_tax_reduction_applicable_income: 0 -- name: The national pensioner scheme takes the full war pension disregard in Kingston upon Thames +- name: The Kingston upon Thames scheme counts a pensioner's war pension less 10 a week period: 2026 absolute_error_margin: 0.01 input: @@ -505,6 +507,62 @@ local_authority: KINGSTON_UPON_THAMES council_tax: 2_000 savings: 0 + output: + council_tax_reduction_pensioner: true + # Sch 6 para 1: 5,000 - 520. + council_tax_reduction_applicable_income: 5_000 - 10 * 52 + +- name: The Kingston upon Thames pensioner schedule has no family limit on war pension disregards + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + afcs_reported: 5_000 + war_widows_pension_reported: 3_000 + partner: + age: 70 + afcs_reported: 5_000 + war_widows_pension_reported: 3_000 + benunits: + benunit: + members: [pensioner, partner] + would_claim_pc: false + households: + household: + members: [pensioner, partner] + country: ENGLAND + region: LONDON + local_authority: KINGSTON_UPON_THAMES + council_tax: 2_000 + savings: 0 + output: + council_tax_reduction_pensioner: true + # Four payments, four disregards of 520; Sch 8 para 40 is a working-age + # limit: 16,000 - 2,080. + council_tax_reduction_applicable_income: 16_000 - 4 * 10 * 52 + +- name: The national pensioner scheme takes the full war pension disregard outside the councils the model reads + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + afcs_reported: 5_000 + benunits: + benunit: + members: [pensioner] + would_claim_pc: false + households: + household: + members: [pensioner] + country: ENGLAND + region: SOUTH_EAST + local_authority: MAIDSTONE + council_tax: 2_000 + savings: 0 output: council_tax_reduction_pensioner: true council_tax_reduction_applicable_income: 0 diff --git a/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py b/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py index 31ea275684..11691d4e48 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py @@ -23,9 +23,11 @@ them (SSI 2012/319 Sch 3 para 1; SSI 2021/249 reg 57(1)), and English and Welsh councils may disregard more than the prescribed £10 a week (WSI 2013/3029 reg 34(5); LGFA 1992 Sch 1A para 2(11) and s.13A(1)(c)). Kingston upon Thames -(Sch 8 para 20) and Newham (Sch 8 para 19) disregard only £10 a week of each -payment at working age, and Kingston limits the family's total to £20 a week -(Sch 8 para 40). Westminster, Merton and Oxford disregard them in full. +and Newham disregard only £10 a week of each payment, at working age (Kingston +Sch 8 para 20, Newham Sch 8 para 19) and at pension age (each council's Sch 6 +para 1), and Kingston limits a working-age family's total to £20 a week (Sch 8 +para 40). Westminster's, Merton's and Oxford's working-age schemes disregard +them in full. Invariants, in 2026, for single people and couples with no children: @@ -47,10 +49,11 @@ strictly between nil and the liability, it falls by 20% of the rise in income. 3. War pensions: adding war pensions leaves Council Tax Reduction income - unchanged under the general rules, except under the Kingston upon Thames - and Newham working-age schemes, where it rises by the payments less £10 a - week of each (in Kingston, less at most £20 a week in all). The disregards - are written out here from the schemes, not read from the parameters. + unchanged under the general rules, except in Kingston upon Thames and + Newham, where it rises by the payments less £10 a week of each (for a + working-age family in Kingston, less at most £20 a week in all). The + disregards are written out here from the schemes, not read from the + parameters. These compare the model's own measures. Invariant 1 relies on both measures deducting the family's whole income tax and National Insurance, an existing @@ -84,6 +87,7 @@ PENSION_AGE_AREAS = { "MAIDSTONE": ("ENGLAND", "SOUTH_EAST"), "KINGSTON_UPON_THAMES": ("ENGLAND", "LONDON"), + "NEWHAM": ("ENGLAND", "LONDON"), "CARDIFF": ("WALES", "WALES"), "GLASGOW_CITY": ("SCOTLAND", "SCOTLAND"), } @@ -370,12 +374,13 @@ def war_pensions(draw): } -def local_war_pension_income(payments, area): - """Counted war pensions under Kingston's Sch 8 para 20 and 40 or - Newham's Sch 8 para 19, from each adult's payments.""" +def local_war_pension_income(payments, area, pensioner): + """Counted war pensions under Kingston's or Newham's scheme (Sch 6 para + 1 at pension age; Kingston Sch 8 paras 20 and 40 and Newham Sch 8 para 19 + at working age), from each adult's payments.""" amounts = [x for person in payments for x in person.values()] disregard = sum(min(x, LOCAL_WAR_PENSION_DISREGARD) for x in amounts) - if area == "KINGSTON_UPON_THAMES": + if area == "KINGSTON_UPON_THAMES" and not pensioner: disregard = min(disregard, KINGSTON_DISREGARD_LIMIT) return sum(amounts) - disregard @@ -403,8 +408,8 @@ def test_war_pensions_count_only_where_the_scheme_says(data): general = (r[base] == 0) & (r[plus] == 0) & (income[base] > 0) expected = np.array( [ - local_war_pension_income(pay, fam["area"]) - if fam["area"] in LOCAL_WAR_PENSION_AREAS and not pensioner[2 * k] + local_war_pension_income(pay, fam["area"], pensioner[2 * k]) + if fam["area"] in LOCAL_WAR_PENSION_AREAS else 0.0 for k, (fam, pay) in enumerate(zip(families, payments)) ] diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py index b37764428a..a3189661be 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py @@ -1,7 +1,7 @@ from policyengine_uk.model_api import * from policyengine_uk.variables.gov.local_authorities.council_tax_reduction.config import ( - is_kingston_upon_thames_working_age, - is_newham_working_age, + is_kingston_upon_thames_area, + is_newham_area, ) from policyengine_uk.variables.household.demographic.country import Country @@ -25,10 +25,10 @@ class council_tax_reduction_applicable_income(Variable): "injuries disablement benefit, severe disablement allowance and " "incapacity benefit count in every scheme. War pensions and Armed " "Forces Compensation Scheme payments count, less £10 a week each, " - "only under the Kingston upon Thames and Newham working-age schemes: " - "Scotland disregards or omits them, and elsewhere in England and " - "Wales councils may disregard them beyond the prescribed £10 a week, " - "so the model takes the full disregard." + "only under the Kingston upon Thames and Newham schemes: Scotland " + "disregards or omits them, and elsewhere in England and Wales " + "councils may disregard them beyond the prescribed £10 a week, so the " + "model takes the full disregard." ) definition_period = YEAR unit = GBP @@ -55,6 +55,8 @@ class council_tax_reduction_applicable_income(Variable): "https://www.legislation.gov.uk/ukpga/1992/14/schedule/1A/paragraph/2", "https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=136", "https://www.newham.gov.uk/downloads/file/10753/council-tax-reduction-scheme-2026#page=292", + "https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=127", + "https://www.newham.gov.uk/downloads/file/10753/council-tax-reduction-scheme-2026#page=273", ] def formula(benunit, period, parameters): @@ -147,13 +149,15 @@ def formula(benunit, period, parameters): # schemes under SSAA 1992 s.134(8)(a). Reg 34(5) does not cover # guaranteed income payments, so in Wales that over-disregards them, # but afcs does not separate them from war disablement pensions. - # Of the English working-age schemes the model runs, Westminster's, - # Oxford's and Merton's disregard war pensions in full. Kingston upon - # Thames (Sch 8 para 20) and Newham (Sch 8 para 19) disregard £10 a - # week of each payment, the reading Pension Credit's identically - # worded disregard takes, and Kingston limits the family's total to - # £20 a week (Sch 8 para 40). Newham's limit (its para 39) names its - # paras 20 and 21, not the war pension para 19, so it does not apply. + # Of the councils whose schemes the model reads, Westminster's, + # Oxford's and Merton's working-age schemes disregard war pensions in + # full. Kingston upon Thames and Newham disregard only £10 a week of + # each payment, the reading Pension Credit's identically worded + # disregard takes, at working age (Kingston Sch 8 para 20, Newham Sch 8 + # para 19) and at pension age (each council's Sch 6 para 1). Kingston + # limits a working-age family's total to £20 a week (Sch 8 para 40); + # Newham's limit (its para 39) names its paras 20 and 21, not the war + # pension para 19, and neither pensioner schedule has a limit. income_components = [ "employment_income", "self_employment_income", @@ -188,10 +192,8 @@ def formula(benunit, period, parameters): increased_income = income + personal_benefit_income + credits + benefits local_authority = benunit.household("local_authority", period) - kingston = is_kingston_upon_thames_working_age( - local_authority, country, pensioner - ) - newham = is_newham_working_age(local_authority, country, pensioner) + kingston = is_kingston_upon_thames_area(local_authority, country) + newham = is_newham_area(local_authority, country) local_authorities = parameters(period).gov.local_authorities kingston_test = ( local_authorities.kingston_upon_thames.council_tax_reduction.means_test @@ -211,7 +213,7 @@ def formula(benunit, period, parameters): min_(amount, benunit.project(payment_disregard)) ) war_pension_disregards = where( - kingston, + kingston & ~pensioner, min_( war_pension_disregards, kingston_test.war_pension_disregard_limit * WEEKS_IN_YEAR, From e135b13ab8236f70f36ab756789b297a42bd4475 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 00:41:19 -0400 Subject: [PATCH 05/10] Keep war pensions uncounted and count only the applicant's and partner's benefits MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review of f0273113f found that Newham's s.13A(1)(c) policy (para 2.1) adds, automatically, the reduction a full disregard of war compensation payments would give, and Kingston upon Thames's para 36(6) speaks of a full disregard while its schedules keep the £10. So the £10 branch for those councils overstated their council tax; war pensions stay uncounted everywhere, with each council's text noted. IIDB, SDA and IB now count for the applicant and partner only (SI 2012/2885 Sch 1 para 11; SSI 2021/249 reg 36(1)), not for dependants that the wider member mask still reaches. The award cases asserting the 256.00 pensioner allowance use claimants who reached pensionable age before April 2021. New properties: war pensions never count, and dependants' benefits never count. Part of #2510. Co-Authored-By: Claude Opus 5.5 --- .../ctr-income-prescribed-benefits.fixed.md | 2 +- .../means_test/war_pension_disregard.yaml | 12 -- .../war_pension_disregard_limit.yaml | 10 - .../means_test/war_pension_disregard.yaml | 12 -- ...cil_tax_reduction_prescribed_benefits.yaml | 202 ++++-------------- ...eduction_prescribed_benefits_properties.py | 123 ++++++----- ...council_tax_reduction_applicable_income.py | 126 +++++------ 7 files changed, 157 insertions(+), 330 deletions(-) delete mode 100644 policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml delete mode 100644 policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard_limit.yaml delete mode 100644 policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml diff --git a/changelog.d/ctr-income-prescribed-benefits.fixed.md b/changelog.d/ctr-income-prescribed-benefits.fixed.md index 3915edbe55..8591acdafc 100644 --- a/changelog.d/ctr-income-prescribed-benefits.fixed.md +++ b/changelog.d/ctr-income-prescribed-benefits.fixed.md @@ -1 +1 @@ -Council Tax Reduction income now counts industrial injuries disablement benefit, severe disablement allowance and incapacity benefit in every scheme (SI 2012/2885 Sch 1 para 16; SI 2012/2886 Sch paras 39 and 54; WSI 2013/3029 Sch 1 para 10 and Sch 6 para 17; SSI 2012/319 reg 27; SSI 2021/249 reg 57), and counts war pensions and Armed Forces Compensation Scheme payments less £10 a week each under the Kingston upon Thames and Newham schemes. +Council Tax Reduction income now counts the applicant's and partner's industrial injuries disablement benefit, severe disablement allowance and incapacity benefit in every scheme (SI 2012/2885 Sch 1 para 16; SI 2012/2886 Sch paras 39 and 54; WSI 2013/3029 Sch 1 para 10 and Sch 6 para 17; SSI 2012/319 reg 27; SSI 2021/249 reg 57). diff --git a/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml b/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml deleted file mode 100644 index 4601fb6c5b..0000000000 --- a/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard.yaml +++ /dev/null @@ -1,12 +0,0 @@ -description: Weekly amount of each war disablement pension, war widow's or widower's pension and Armed Forces Compensation Scheme guaranteed income payment disregarded from an applicant's income under the Kingston upon Thames Council Tax Reduction scheme, at working age and at pension age. -values: - 2026-04-01: 10 -metadata: - unit: currency-GBP - period: week - label: Kingston upon Thames Council Tax Reduction war pension disregard - reference: - - title: Council Tax Reduction Scheme 2026 - 2027, Sch 8 para 20 (persons who are not pensioners, PDF page 136) - href: https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=136 - - title: Council Tax Reduction Scheme 2026 - 2027, Sch 6 para 1 (pensioners, PDF page 127) - href: https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=127 diff --git a/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard_limit.yaml b/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard_limit.yaml deleted file mode 100644 index 2a50f0ff9f..0000000000 --- a/policyengine_uk/parameters/gov/local_authorities/kingston_upon_thames/council_tax_reduction/means_test/war_pension_disregard_limit.yaml +++ /dev/null @@ -1,10 +0,0 @@ -description: Weekly limit on a working-age family's total disregards under Sch 8 paras 20 and 21 (war pensions and widowed parent's allowance) and the student income disregards, under the Kingston upon Thames Council Tax Reduction scheme. -values: - 2026-04-01: 20 -metadata: - unit: currency-GBP - period: week - label: Kingston upon Thames Council Tax Reduction war pension disregard limit - reference: - - title: Council Tax Reduction Scheme 2026 - 2027, Sch 8 para 40 (PDF page 139) - href: https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=139 diff --git a/policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml b/policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml deleted file mode 100644 index 966221539d..0000000000 --- a/policyengine_uk/parameters/gov/local_authorities/newham/council_tax_reduction/means_test/war_pension_disregard.yaml +++ /dev/null @@ -1,12 +0,0 @@ -description: Weekly amount of each war disablement pension, war widow's or widower's pension and Armed Forces Compensation Scheme guaranteed income payment disregarded from an applicant's income under the Newham Council Tax Reduction scheme, at working age and at pension age. -values: - 2026-04-01: 10 -metadata: - unit: currency-GBP - period: week - label: Newham Council Tax Reduction war pension disregard - reference: - - title: Council Tax Reduction Scheme 2026, Sch 8 para 19 (persons who are not pensioners, PDF page 292) - href: https://www.newham.gov.uk/downloads/file/10753/council-tax-reduction-scheme-2026#page=292 - - title: Council Tax Reduction Scheme 2026, Sch 6 para 1 (pensioners, PDF page 273) - href: https://www.newham.gov.uk/downloads/file/10753/council-tax-reduction-scheme-2026#page=273 diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml index ce66ea45fd..94b91ccdbd 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml @@ -24,12 +24,16 @@ # exceed (WSI 2013/3029 reg 34(5); LGFA 1992 Sch 1A para 2(11) and # s.13A(1)(c)). The model takes the full disregard where it cannot see the # council's choice. -# - Of the councils whose schemes the model reads, Westminster's, Oxford's and -# Merton's working-age schemes disregard war pensions in full. Kingston upon -# Thames and Newham disregard £10 a week of each payment at working age -# (Kingston Sch 8 para 20, Newham Sch 8 para 19) and at pension age (each -# council's Sch 6 para 1). Kingston limits a working-age family's total to -# £20 a week (Sch 8 para 40); neither pensioner schedule has a limit. +# - Of the councils whose schemes the model reads, Westminster disregards war +# pensions in full and Oxford ignores them for applicants without Universal +# Credit. Newham's scheme keeps the £10, but its discretionary reduction +# policy (para 2.1) automatically adds what a full disregard would give. +# Kingston upon Thames's schedules keep the £10 while its para 36(6) speaks +# of a full disregard, and Merton's "in full" disregard is "subject to +# paragraph 40" (a £20 a week limit). The model approximates all of them +# with the full disregard. +# - Only the applicant's and partner's IIDB, SDA and IB count (SI 2012/2885 +# Sch 1 para 11; SSI 2021/249 reg 36(1)), not a dependant's. # # Weekly rates, 2026-27 (SI 2026/148 Sch 1, restating SSCBA 1992 Sch 4): # disablement pension at 100% 233.90, 50% 116.95, 20% 46.78; long-term @@ -38,9 +42,13 @@ # 241.30 (SI 2026/148 art 6(1)), 12,547.60 a year, below the 12,570 personal # allowance. IIDB, SDA and war pensions are not taxed in the model; State # Pension and IB are. Pension Credit standard minimum guarantee 238.00 -# (single). CTR applicable amounts: personal allowance 256.00 (single, pension -# age) and 95.55 (single, 25 or over); the reduction is withdrawn at 20% of -# income above the applicable amount. Council tax is 2,000 throughout. +# (single). CTR applicable amounts: personal allowance 256.00 for a single +# person who reached pensionable age before 1 April 2021 (SI 2012/2885 Sch 2 +# para 1, as uprated by SI 2026/27 reg 6; SSI 2026/56 reg 6 in Scotland), so +# the pensioners with an award asserted below are 80; and 95.55 for a single +# person aged 25 or over in Scotland's working-age scheme (SSI 2026/56 reg +# 14). The reduction is withdrawn at 20% of income above the applicable +# amount. Council tax is 2,000 throughout. - name: IIDB counts as pension-age Council Tax Reduction income in England period: 2026 @@ -48,7 +56,7 @@ input: people: pensioner: - age: 70 + age: 80 state_pension: 241.30 * 52 iidb_reported: 116.95 * 52 benunits: @@ -104,7 +112,7 @@ input: people: pensioner: - age: 70 + age: 80 state_pension: 241.30 * 52 iidb_reported: 46.78 * 52 benunits: @@ -331,86 +339,7 @@ output: council_tax_reduction_applicable_income: 0 -- name: The Kingston upon Thames working-age scheme counts a war pension less 10 a week - period: 2026 - absolute_error_margin: 0.01 - input: - people: - claimant: - age: 40 - afcs_reported: 5_000 - benunits: - benunit: - members: [claimant] - would_claim_uc: false - households: - household: - members: [claimant] - country: ENGLAND - region: LONDON - local_authority: KINGSTON_UPON_THAMES - council_tax: 2_000 - savings: 0 - output: - # 5,000 - 520 - council_tax_reduction_applicable_income: 5_000 - 10 * 52 - -- name: The Kingston upon Thames scheme disregards 10 a week of each war pension - period: 2026 - absolute_error_margin: 0.01 - input: - people: - claimant: - age: 40 - afcs_reported: 5_000 - war_widows_pension_reported: 3_000 - benunits: - benunit: - members: [claimant] - would_claim_uc: false - households: - household: - members: [claimant] - country: ENGLAND - region: LONDON - local_authority: KINGSTON_UPON_THAMES - council_tax: 2_000 - savings: 0 - output: - # 8,000 - 2 x 520: two payments, two disregards, within the 20 a week limit. - council_tax_reduction_applicable_income: 8_000 - 2 * 10 * 52 - -- name: The Kingston upon Thames scheme limits a family's war pension disregards to 20 a week - period: 2026 - absolute_error_margin: 0.01 - input: - people: - claimant: - age: 40 - afcs_reported: 5_000 - war_widows_pension_reported: 3_000 - partner: - age: 40 - afcs_reported: 5_000 - war_widows_pension_reported: 3_000 - benunits: - benunit: - members: [claimant, partner] - would_claim_uc: false - households: - household: - members: [claimant, partner] - country: ENGLAND - region: LONDON - local_authority: KINGSTON_UPON_THAMES - council_tax: 2_000 - savings: 0 - output: - # Four payments would give 4 x 10 a week; Sch 8 para 40 limits the - # family's total to 20 a week: 16,000 - 1,040. - council_tax_reduction_applicable_income: 16_000 - 20 * 52 - -- name: The Newham working-age scheme disregards 10 a week of each war pension, with no family limit +- name: The Westminster working-age scheme disregards war pensions in full period: 2026 absolute_error_margin: 0.01 input: @@ -419,35 +348,6 @@ age: 40 afcs_reported: 5_000 war_widows_pension_reported: 3_000 - partner: - age: 40 - afcs_reported: 5_000 - war_widows_pension_reported: 3_000 - benunits: - benunit: - members: [claimant, partner] - would_claim_uc: false - households: - household: - members: [claimant, partner] - country: ENGLAND - region: LONDON - local_authority: NEWHAM - council_tax: 2_000 - savings: 0 - output: - # Newham's limit (its Sch 8 para 39) names paras 20 and 21, not the war - # pension para 19: 16,000 - 4 x 520. - council_tax_reduction_applicable_income: 16_000 - 4 * 10 * 52 - -- name: A war pension below 10 a week counts as nothing under the Newham scheme - period: 2026 - absolute_error_margin: 0.01 - input: - people: - claimant: - age: 40 - afcs_reported: 300 benunits: benunit: members: [claimant] @@ -457,13 +357,13 @@ members: [claimant] country: ENGLAND region: LONDON - local_authority: NEWHAM + local_authority: WESTMINSTER council_tax: 2_000 savings: 0 output: council_tax_reduction_applicable_income: 0 -- name: The Westminster working-age scheme disregards war pensions in full +- name: War pensions do not count under the Kingston upon Thames working-age scheme period: 2026 absolute_error_margin: 0.01 input: @@ -481,13 +381,13 @@ members: [claimant] country: ENGLAND region: LONDON - local_authority: WESTMINSTER + local_authority: KINGSTON_UPON_THAMES council_tax: 2_000 savings: 0 output: council_tax_reduction_applicable_income: 0 -- name: The Kingston upon Thames scheme counts a pensioner's war pension less 10 a week +- name: War pensions do not count for a pensioner in Newham period: 2026 absolute_error_margin: 0.01 input: @@ -504,68 +404,40 @@ members: [pensioner] country: ENGLAND region: LONDON - local_authority: KINGSTON_UPON_THAMES - council_tax: 2_000 - savings: 0 - output: - council_tax_reduction_pensioner: true - # Sch 6 para 1: 5,000 - 520. - council_tax_reduction_applicable_income: 5_000 - 10 * 52 - -- name: The Kingston upon Thames pensioner schedule has no family limit on war pension disregards - period: 2026 - absolute_error_margin: 0.01 - input: - people: - pensioner: - age: 70 - afcs_reported: 5_000 - war_widows_pension_reported: 3_000 - partner: - age: 70 - afcs_reported: 5_000 - war_widows_pension_reported: 3_000 - benunits: - benunit: - members: [pensioner, partner] - would_claim_pc: false - households: - household: - members: [pensioner, partner] - country: ENGLAND - region: LONDON - local_authority: KINGSTON_UPON_THAMES + local_authority: NEWHAM council_tax: 2_000 savings: 0 output: council_tax_reduction_pensioner: true - # Four payments, four disregards of 520; Sch 8 para 40 is a working-age - # limit: 16,000 - 2,080. - council_tax_reduction_applicable_income: 16_000 - 4 * 10 * 52 + council_tax_reduction_applicable_income: 0 -- name: The national pensioner scheme takes the full war pension disregard outside the councils the model reads +- name: A dependant's IIDB does not count as Council Tax Reduction income period: 2026 absolute_error_margin: 0.01 input: people: pensioner: - age: 70 - afcs_reported: 5_000 + age: 80 + state_pension: 241.30 * 52 + dependant: + age: 17 + iidb_reported: 46.78 * 52 benunits: benunit: - members: [pensioner] + members: [pensioner, dependant] would_claim_pc: false households: household: - members: [pensioner] + members: [pensioner, dependant] country: ENGLAND region: SOUTH_EAST local_authority: MAIDSTONE council_tax: 2_000 savings: 0 output: - council_tax_reduction_pensioner: true - council_tax_reduction_applicable_income: 0 + # Only the applicant's and partner's income counts (Sch 1 para 11); + # pensioner schemes also exclude Child Benefit. + council_tax_reduction_applicable_income: 241.30 * 52 - name: A guarantee credit recipient with IIDB has nil Council Tax Reduction income period: 2026 diff --git a/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py b/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py index 11691d4e48..037f5d96f5 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py @@ -18,18 +18,21 @@ guarantee credit passport ends, Council Tax Reduction was assessed on less income than Pension Credit and Housing Benefit. -War pensions and Armed Forces Compensation Scheme payments stay uncounted -where the model cannot see the council's choice: Scotland disregards or omits -them (SSI 2012/319 Sch 3 para 1; SSI 2021/249 reg 57(1)), and English and -Welsh councils may disregard more than the prescribed £10 a week (WSI 2013/3029 -reg 34(5); LGFA 1992 Sch 1A para 2(11) and s.13A(1)(c)). Kingston upon Thames -and Newham disregard only £10 a week of each payment, at working age (Kingston -Sch 8 para 20, Newham Sch 8 para 19) and at pension age (each council's Sch 6 -para 1), and Kingston limits a working-age family's total to £20 a week (Sch 8 -para 40). Westminster's, Merton's and Oxford's working-age schemes disregard -them in full. - -Invariants, in 2026, for single people and couples with no children: +Only the applicant's and partner's benefits count (SI 2012/2885 Sch 1 para 11; +SSI 2021/249 reg 36(1)), not a dependant's. + +War pensions and Armed Forces Compensation Scheme payments stay uncounted. +Scotland disregards or omits them (SSI 2012/319 Sch 3 para 1; SSI 2021/249 reg +57(1)), and English and Welsh councils may relieve more than the prescribed +£10 a week (WSI 2013/3029 reg 34(5); LGFA 1992 Sch 1A para 2(11) and +s.13A(1)(c)). The councils whose schemes the model reads mostly do: Westminster +and Oxford disregard them in full, Newham's discretionary policy adds what a +full disregard would give, and Kingston upon Thames's and Merton's texts point +both ways. The model approximates all of them with the full disregard. + +Invariants, in 2026, for single people and couples not claiming Universal +Credit (whose award would move with the benefits), with no children except in +invariant 4: 1. Differential: Council Tax Reduction and Pension Credit assess the same income. For pension-age families under the national pensioner schemes in @@ -48,12 +51,12 @@ credit) is unchanged; in the national schemes, where both awards are strictly between nil and the liability, it falls by 20% of the rise in income. -3. War pensions: adding war pensions leaves Council Tax Reduction income - unchanged under the general rules, except in Kingston upon Thames and - Newham, where it rises by the payments less £10 a week of each (for a - working-age family in Kingston, less at most £20 a week in all). The - disregards are written out here from the schemes, not read from the - parameters. +3. War pensions: adding war pensions and Armed Forces Compensation Scheme + payments leaves Council Tax Reduction income unchanged under the general + rules, in every scheme. +4. Dependants: giving a dependant aged 16 to 19 IIDB, IB within the personal + allowance, or SDA leaves Council Tax Reduction income unchanged under the + general rules, in every scheme. These compare the model's own measures. Invariant 1 relies on both measures deducting the family's whole income tax and National Insurance, an existing @@ -73,9 +76,6 @@ LONG_TERM_IB = 146.60 * WEEKS SDA = (103.85 + 15.50) * WEEKS WITHDRAWAL_RATE = 0.2 -# £10 a week of each war pension; Kingston's £20 a week family limit. -LOCAL_WAR_PENSION_DISREGARD = 10 * WEEKS -KINGSTON_DISREGARD_LIMIT = 20 * WEEKS PRIVATE_PENSIONS = np.arange(0, 20_001, 1_000) PROPERTY_SETTINGS = settings( max_examples=10, @@ -101,7 +101,6 @@ "GLASGOW_CITY": ("SCOTLAND", "SCOTLAND"), } NATIONAL_SCHEME_AREAS = {"MAIDSTONE", "CARDIFF", "GLASGOW_CITY"} -LOCAL_WAR_PENSION_AREAS = {"KINGSTON_UPON_THAMES", "NEWHAM"} BENUNIT_VARIABLES = [ "council_tax_reduction_applicable_income", "council_tax_benefit", @@ -189,8 +188,7 @@ def working_age_family(draw): savings=0.0, would_claim_pc=False, ) -# Two adults on the Kingston upon Thames working-age scheme, so the £20 limit -# binds once each has two war pensions. +# A working-age couple on the Kingston upon Thames scheme. KINGSTON_COUPLE = dict( ages=[40, 40], state_pensions=[0.0, 0.0], @@ -237,6 +235,13 @@ def situation(variants): person[variable] = {YEAR: value} people[name] = person names.append(name) + if fam.get("dependant_age") is not None: + j = len(fam["ages"]) + name = f"p{i}_{j}" + people[name] = {"age": {YEAR: fam["dependant_age"]}} + for variable, value in extra.get(j, {}).items(): + people[name][variable] = {YEAR: value} + names.append(name) benunits[f"b{i}"] = { "members": names, "would_claim_pc": {YEAR: fam["would_claim_pc"]}, @@ -374,22 +379,11 @@ def war_pensions(draw): } -def local_war_pension_income(payments, area, pensioner): - """Counted war pensions under Kingston's or Newham's scheme (Sch 6 para - 1 at pension age; Kingston Sch 8 paras 20 and 40 and Newham Sch 8 para 19 - at working age), from each adult's payments.""" - amounts = [x for person in payments for x in person.values()] - disregard = sum(min(x, LOCAL_WAR_PENSION_DISREGARD) for x in amounts) - if area == "KINGSTON_UPON_THAMES" and not pensioner: - disregard = min(disregard, KINGSTON_DISREGARD_LIMIT) - return sum(amounts) - disregard - - @PROPERTY_SETTINGS @given(st.data()) -def test_war_pensions_count_only_where_the_scheme_says(data): - # Every draw ends with the Kingston couple, whose four payments meet the - # £20 a week limit, so the local branch is always exercised. +def test_war_pensions_never_count(data): + # Every draw includes the Kingston couple, so a council whose schedules + # keep the £10 is always exercised. families = data.draw(st.lists(any_family, min_size=1, max_size=6)) families = families + [KINGSTON_COUPLE] payments = [[data.draw(war_pensions()) for _ in fam["ages"]] for fam in families] @@ -404,20 +398,43 @@ def test_war_pensions_count_only_where_the_scheme_says(data): base, plus = slice(0, None, 2), slice(1, None, 2) income = v["council_tax_reduction_applicable_income"] r = route(v) - pensioner = v["council_tax_reduction_pensioner"].astype(bool) - general = (r[base] == 0) & (r[plus] == 0) & (income[base] > 0) - expected = np.array( - [ - local_war_pension_income(pay, fam["area"], pensioner[2 * k]) - if fam["area"] in LOCAL_WAR_PENSION_AREAS - else 0.0 - for k, (fam, pay) in enumerate(zip(families, payments)) - ] - ) + general = (r[base] == 0) & (r[plus] == 0) + assert general[-1] + rise = income[plus] - income[base] + assert np.allclose(rise[general], 0, atol=0.01), [ + (families[k]["area"], payments[k], rise[k]) + for k in np.flatnonzero(general & ~np.isclose(rise, 0, atol=0.01))[:3] + ] + + +DEPENDANT_CHANGES = { + "iidb": {"iidb_reported": 0.5 * IIDB_FULL}, + "incapacity_benefit": {"incapacity_benefit_reported": LONG_TERM_IB}, + "sda": {"sda_reported": 1.0}, +} + + +@PROPERTY_SETTINGS +@given( + st.lists(any_family, min_size=1, max_size=6), + st.sampled_from(sorted(DEPENDANT_CHANGES)), + st.integers(16, 19), +) +@example([GENERAL_RULES_PENSIONER, KINGSTON_COUPLE], "iidb", 17) +def test_dependants_benefits_never_count(families, change, dependant_age): + families = [{**fam, "dependant_age": dependant_age} for fam in families] + variants = [] + for fam in families: + dependant = len(fam["ages"]) + variants.append((fam, 5_000.0, {})) + variants.append((fam, 5_000.0, {dependant: DEPENDANT_CHANGES[change]})) + v = calculate(variants) + base, plus = slice(0, None, 2), slice(1, None, 2) + income = v["council_tax_reduction_applicable_income"] + r = route(v) + general = (r[base] == 0) & (r[plus] == 0) rise = income[plus] - income[base] - assert np.allclose(rise[general], expected[general], atol=0.01), [ - (families[k]["area"], payments[k], rise[k], expected[k]) - for k in np.flatnonzero(general & ~np.isclose(rise, expected, atol=0.01))[:3] + assert np.allclose(rise[general], 0, atol=0.01), [ + (families[k]["area"], families[k]["ages"], change, rise[k]) + for k in np.flatnonzero(general & ~np.isclose(rise, 0, atol=0.01))[:3] ] - # The Kingston couple's four payments meet the £20 a week limit. - assert np.isclose(rise[-1], 16_000 - KINGSTON_DISREGARD_LIMIT, atol=0.01) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py index a3189661be..848f414049 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py @@ -1,8 +1,4 @@ from policyengine_uk.model_api import * -from policyengine_uk.variables.gov.local_authorities.council_tax_reduction.config import ( - is_kingston_upon_thames_area, - is_newham_area, -) from policyengine_uk.variables.household.demographic.country import Country @@ -23,12 +19,11 @@ class council_tax_reduction_applicable_income(Variable): "provisions allow (childcare charges, the £20 earnings disregards, " "maintenance disregards and the rest) are not modelled. Industrial " "injuries disablement benefit, severe disablement allowance and " - "incapacity benefit count in every scheme. War pensions and Armed " - "Forces Compensation Scheme payments count, less £10 a week each, " - "only under the Kingston upon Thames and Newham schemes: Scotland " - "disregards or omits them, and elsewhere in England and Wales " - "councils may disregard them beyond the prescribed £10 a week, so the " - "model takes the full disregard." + "incapacity benefit count in every scheme, for the applicant and " + "partner. War pensions and Armed Forces Compensation Scheme payments " + "are not counted: Scotland disregards or omits them, and English and " + "Welsh councils may relieve them beyond the prescribed £10 a week, so " + "the model takes the full disregard." ) definition_period = YEAR unit = GBP @@ -53,10 +48,8 @@ class council_tax_reduction_applicable_income(Variable): "https://www.legislation.gov.uk/wsi/2013/3029/schedule/6/paragraph/17", "https://www.legislation.gov.uk/ssi/2012/319/schedule/3/paragraph/1", "https://www.legislation.gov.uk/ukpga/1992/14/schedule/1A/paragraph/2", - "https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=136", - "https://www.newham.gov.uk/downloads/file/10753/council-tax-reduction-scheme-2026#page=292", - "https://www.kingston.gov.uk/sites/default/files/2026-03/Council%20Tax%20Reduction%20Scheme%202026%20-%202027.pdf#page=127", - "https://www.newham.gov.uk/downloads/file/10753/council-tax-reduction-scheme-2026#page=273", + "https://www.legislation.gov.uk/ukpga/1992/14/section/13A", + "https://www.newham.gov.uk/downloads/file/209/discretionary-council-tax-reductions-for-individual-council-tax-payers", ] def formula(benunit, period, parameters): @@ -116,23 +109,26 @@ def formula(benunit, period, parameters): "statutory_sick_pay", "statutory_maternity_pay", "ssmg", - # Every scheme the model runs counts these three benefits. At - # pension age they are social security benefits that the income - # lists do not except: SI 2012/2885 Sch 1 para 16(1)(j), the - # default scheme SI 2012/2886 Sch para 39(1)(j), WSI 2013/3029 - # Sch 1 para 10(1)(j) and SSI 2012/319 reg 27(1)(j). At working - # age England and Wales count gross income (SI 2012/2886 Sch para - # 54(1); WSI 2013/3029 Sch 6 para 17(1)) less disregards that do - # not list them (their Schs 8 and 9), and Scotland's closed list - # names them (SSI 2021/249 reg 57(1)(b)(ix) to (xi)). Each scheme - # leaves out IIDB's constant attendance and exceptionally severe - # disablement increases (SSCBA 1992 ss.104 and 105), by name or as - # "attendance allowance" (Sch 8 and Sch 9 para 14), but iidb is - # the whole reported payment and cannot separate them. - "iidb", - "sda", - "incapacity_benefit", ] + # Every scheme the model runs counts these three benefits. At pension + # age they are social security benefits that the income lists do not + # except: SI 2012/2885 Sch 1 para 16(1)(j), the default scheme SI + # 2012/2886 Sch para 39(1)(j), WSI 2013/3029 Sch 1 para 10(1)(j) and + # SSI 2012/319 reg 27(1)(j). At working age England and Wales count + # gross income (SI 2012/2886 Sch para 54(1); WSI 2013/3029 Sch 6 para + # 17(1)) less disregards that do not list them (their Schs 8 and 9), + # and Scotland's closed list names them (SSI 2021/249 reg 57(1)(b)(ix) + # to (xi)). Each scheme leaves out IIDB's constant attendance and + # exceptionally severe disablement increases (SSCBA 1992 ss.104 and + # 105), by name or as "attendance allowance" (Sch 8 and Sch 9 para + # 14), but iidb is the whole reported payment and cannot separate + # them. Only the applicant's and partner's count (Sch 1 para 11; SSI + # 2021/249 reg 36(1)), so these leave out the dependants that the + # wider mask above still reaches. + applicant_and_partner_benefits = ["iidb", "sda", "incapacity_benefit"] + applicant_and_partner = ( + person("is_claimant_or_partner", period) | applicant_counted_as_young_person + ) # War disablement pensions, war widow's or widower's pensions and # Armed Forces Compensation Scheme payments (afcs, war_widows_pension). # Scotland counts none of them: SSI 2012/319 Sch 3 para 1 disregards @@ -144,20 +140,26 @@ def formula(benunit, period, parameters): # or widower's pensions (WSI 2013/3029 reg 34(5)), an English scheme # may make provision equivalent to the council tax benefit rules (LGFA # 1992 Sch 1A para 2(11)), and any council may reduce council tax - # further (s.13A(1)(c)). Where the model cannot see the council's - # choice it takes the full disregard, as Housing Benefit does for local - # schemes under SSAA 1992 s.134(8)(a). Reg 34(5) does not cover - # guaranteed income payments, so in Wales that over-disregards them, - # but afcs does not separate them from war disablement pensions. - # Of the councils whose schemes the model reads, Westminster's, - # Oxford's and Merton's working-age schemes disregard war pensions in - # full. Kingston upon Thames and Newham disregard only £10 a week of - # each payment, the reading Pension Credit's identically worded - # disregard takes, at working age (Kingston Sch 8 para 20, Newham Sch 8 - # para 19) and at pension age (each council's Sch 6 para 1). Kingston - # limits a working-age family's total to £20 a week (Sch 8 para 40); - # Newham's limit (its para 39) names its paras 20 and 21, not the war - # pension para 19, and neither pensioner schedule has a limit. + # further (s.13A(1)(c)). The model takes the full disregard, as + # Housing Benefit does for local schemes under SSAA 1992 s.134(8)(a). + # The councils whose schemes the model reads mostly go further: + # Westminster's working-age and pensioner schemes disregard war + # pensions in full, and Oxford's Appendix 5 ignores war pensions for + # applicants without Universal Credit. Newham's scheme keeps the £10 + # (Sch 6 para 1, Sch 8 para 19), but since 2013 it adds automatically + # the reduction a full disregard of war compensation payments would + # give (its s.13A(1)(c) policy, para 2.1). Kingston upon Thames's + # schedules keep the £10 (Sch 6 para 1, Sch 8 para 20) while its para + # 36(6) speaks of "the higher amount of full disregard" of the same + # payments. Merton's Sch 8 para 20 disregards them "in full" but + # "subject to paragraph 40", whose £20 a week limit, read literally, + # caps that at working age. These are approximations of the councils' + # texts. The full disregard also over-disregards guaranteed income + # payments in Wales, which reg 34(5) does not cover, and wherever a + # scheme keeps the prescribed £10 without further relief; afcs does + # not separate guaranteed income payments from war disablement + # pensions, nor either from the constant attendance and severe + # disablement additions that every scheme disregards in full. income_components = [ "employment_income", "self_employment_income", @@ -188,42 +190,12 @@ def formula(benunit, period, parameters): personal_benefit_income = add_for_members( benunit, period, personal_benefits, members ) + personal_benefit_income += add_for_members( + benunit, period, applicant_and_partner_benefits, applicant_and_partner + ) credits = add_for_members(benunit, period, ["tax_credits"], members) increased_income = income + personal_benefit_income + credits + benefits - local_authority = benunit.household("local_authority", period) - kingston = is_kingston_upon_thames_area(local_authority, country) - newham = is_newham_area(local_authority, country) - local_authorities = parameters(period).gov.local_authorities - kingston_test = ( - local_authorities.kingston_upon_thames.council_tax_reduction.means_test - ) - newham_test = local_authorities.newham.council_tax_reduction.means_test - payment_disregard = WEEKS_IN_YEAR * where( - kingston, - kingston_test.war_pension_disregard, - newham_test.war_pension_disregard, - ) - war_pensions = 0 - war_pension_disregards = 0 - for payment in ["afcs", "war_widows_pension"]: - amount = person(payment, period) * members - war_pensions += benunit.sum(amount) - war_pension_disregards += benunit.sum( - min_(amount, benunit.project(payment_disregard)) - ) - war_pension_disregards = where( - kingston & ~pensioner, - min_( - war_pension_disregards, - kingston_test.war_pension_disregard_limit * WEEKS_IN_YEAR, - ), - war_pension_disregards, - ) - increased_income += where( - kingston | newham, war_pensions - war_pension_disregards, 0 - ) - if not bi.interactions.include_in_means_tests: increased_income -= add_for_members( benunit, period, ["basic_income"], members From b224d1e8e8290d31ef3da483f94cb2427716ed94 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 00:43:59 -0400 Subject: [PATCH 06/10] Label the Scottish Carer Support Payment exception in the GC boundary property With IB, SDA and IIDB now counted, Council Tax Reduction can end the guarantee credit passport on more income than Pension Credit counted where an overlapping benefit cuts a Scottish carer's Carer Support Payment: Scottish pension-age CTR counts the payment before the CSP Regs reg 16(2) reduction (SSI 2012/319 reg 27(3) and (5)), Pension Credit after it (SPC Regs reg 15(4)(g)). Those families are left out of the boundary invariant until that reading is decided. Part of #2510. Co-Authored-By: Claude Opus 5.5 --- ...nsion_credit_prescribed_benefits_properties.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/policyengine_uk/tests/test_pension_credit_prescribed_benefits_properties.py b/policyengine_uk/tests/test_pension_credit_prescribed_benefits_properties.py index 67448845bd..cbdfaa5d61 100644 --- a/policyengine_uk/tests/test_pension_credit_prescribed_benefits_properties.py +++ b/policyengine_uk/tests/test_pension_credit_prescribed_benefits_properties.py @@ -54,6 +54,14 @@ household net income, excluding the TV licence fee, does not fall. The free TV licence for over-75s on Pension Credit is a genuine statutory cliff, so it is excluded, as in test_pension_credit_carer_income_properties.py. + Intended exception: Scottish pension-age Council Tax Reduction counts + Carer Support Payment before the CSP Regs reg 16(2) overlapping-benefit + reduction (SSI 2012/319 reg 27(3) and (5); see + council_tax_reduction_applicable_income), while Pension Credit counts it + after (SPC Regs reg 15(4)(g)). Where an overlapping benefit cuts the + payment, the passport ends on more Council Tax Reduction income than + Pension Credit counted, so the step can fall. Those families are left out + until that reading is decided. """ import numpy as np @@ -332,13 +340,20 @@ def test_net_income_does_not_fall_where_guarantee_credit_ends(families): "tv_licence", "housing_benefit", "council_tax_benefit", + "carer_support_payment_pre_overlap", + "carer_support_payment", ], ) step = np.diff(g["household_net_income"] + g["tv_licence"], axis=1) gc = g["guarantee_credit"] ends = (gc[:, :-1] > 0) & (gc[:, 1:] <= 0) + # The intended exception in invariant 4: Carer Support Payment cut by an + # overlapping benefit. + csp_cut = g["carer_support_payment_pre_overlap"] > g["carer_support_payment"] + 0.01 for i, fam in enumerate(families): for k in np.flatnonzero(ends[i]): + if csp_cut[i, k] or csp_cut[i, k + 1]: + continue assert step[i, k] >= -0.01, ( fam, f"private pension {PENSIONS[k]} -> {PENSIONS[k + 1]}", From e38883e5d6221b539c8017fbc6e09bff8a816976 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 00:58:25 -0400 Subject: [PATCH 07/10] Count IIDB, SDA and IB for the Council Tax Reduction applicant and partner The benefit unit's claimant-or-partner flag picks the wrong people where a household head applies alone: it counted another couple's IIDB against the head's claim and left out her own. Use is_council_tax_reduction_applicant_or_partner, which is the head alone in that case and the claimant and partner otherwise. Two YAML cases cover it. Part of #2510. Co-Authored-By: Claude Opus 5.5 --- ...cil_tax_reduction_prescribed_benefits.yaml | 80 +++++++++++++++++++ ...council_tax_reduction_applicable_income.py | 9 ++- 2 files changed, 85 insertions(+), 4 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml index 94b91ccdbd..458720e4e5 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml @@ -439,6 +439,86 @@ # pensioner schemes also exclude Child Benefit. council_tax_reduction_applicable_income: 241.30 * 52 +- name: Where a household head applies alone, another couple's IIDB in her benefit unit does not count + period: 2026 + absolute_error_margin: 0.01 + input: + people: + grandmother: + age: 80 + is_household_head: true + mother: + age: 45 + is_parent: true + is_household_head: false + iidb_reported: 233.90 * 52 + father: + age: 46 + is_parent: true + is_household_head: false + iidb_reported: 233.90 * 52 + child: + age: 10 + is_household_head: false + benunits: + family: + members: [grandmother, mother, father, child] + would_claim_uc: false + would_claim_pc: false + would_claim_child_benefit: false + households: + home: + members: [grandmother, mother, father, child] + country: ENGLAND + region: SOUTH_EAST + local_authority: MAIDSTONE + council_tax: 1_800 + savings: 0 + output: + # The grandmother is the applicant and has no partner here (SI 2012/2885 + # Sch 1 para 11); the parents are not her partners. + council_tax_reduction_head_applies_alone: true + is_council_tax_reduction_applicant_or_partner: [true, false, false, false] + council_tax_reduction_applicable_income: 0 + +- name: Where a household head applies alone, her own IIDB counts + period: 2026 + absolute_error_margin: 0.01 + input: + people: + grandmother: + age: 80 + is_household_head: true + iidb_reported: 116.95 * 52 + mother: + age: 45 + is_parent: true + is_household_head: false + father: + age: 46 + is_parent: true + is_household_head: false + child: + age: 10 + is_household_head: false + benunits: + family: + members: [grandmother, mother, father, child] + would_claim_uc: false + would_claim_pc: false + would_claim_child_benefit: false + households: + home: + members: [grandmother, mother, father, child] + country: ENGLAND + region: SOUTH_EAST + local_authority: MAIDSTONE + council_tax: 1_800 + savings: 0 + output: + council_tax_reduction_head_applies_alone: true + council_tax_reduction_applicable_income: 116.95 * 52 + - name: A guarantee credit recipient with IIDB has nil Council Tax Reduction income period: 2026 absolute_error_margin: 0.01 diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py index 848f414049..25405fef5b 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_applicable_income.py @@ -123,11 +123,12 @@ def formula(benunit, period, parameters): # 105), by name or as "attendance allowance" (Sch 8 and Sch 9 para # 14), but iidb is the whole reported payment and cannot separate # them. Only the applicant's and partner's count (Sch 1 para 11; SSI - # 2021/249 reg 36(1)), so these leave out the dependants that the - # wider mask above still reaches. + # 2021/249 reg 36(1)): the Council Tax Reduction applicant and partner, + # which is the household head alone where the head applies alone, and + # never the dependants that the wider mask above still reaches. applicant_and_partner_benefits = ["iidb", "sda", "incapacity_benefit"] - applicant_and_partner = ( - person("is_claimant_or_partner", period) | applicant_counted_as_young_person + applicant_and_partner = person( + "is_council_tax_reduction_applicant_or_partner", period ) # War disablement pensions, war widow's or widower's pensions and # Armed Forces Compensation Scheme payments (afcs, war_widows_pension). From 1ae527d7ce5c1ed1ae2b415564f352416d2b112f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 02:53:23 -0400 Subject: [PATCH 08/10] Declare claimant and dependant roles in the CTR prescribed-benefit properties CI found a draw where a dependant of 17 was under 16 years younger than a claimant of 32, so the model's presumption read them as a partner and their IIDB counted. The generated situations now set is_claimant_or_partner for every member, and that draw is pinned as an explicit example. Part of #2510. Co-Authored-By: Claude Opus 5.5 --- ...eduction_prescribed_benefits_properties.py | 28 ++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py b/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py index 037f5d96f5..6e1541fa8c 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py @@ -202,6 +202,20 @@ def working_age_family(draw): ) +# A working-age lone adult in Westminster, close in age to a dependant. +YOUNG_PARENT = dict( + ages=[32], + state_pensions=[0.0], + earnings=[12_000.0], + benefits=[dict(iidb=0.0, incapacity_benefit=0.0, sda=False)], + property_income=0.0, + area="WESTMINSTER", + council_tax=2_000.0, + savings=0.0, + would_claim_pc=False, +) + + def area_of(fam): if fam["ages"][0] >= 67: return PENSION_AGE_AREAS[fam["area"]] @@ -223,6 +237,12 @@ def situation(variants): b = fam["benefits"][j] person = { "age": {YEAR: age}, + # The generated adults are the claimant and partner, and any + # dependant is not: say so for everyone, so the model's + # presumption (a member under 20 and 16 or more years younger + # than the claimant is their child) cannot read a dependant + # close in age to the claimant as a partner. + "is_claimant_or_partner": {YEAR: True}, "state_pension": {YEAR: fam["state_pensions"][j]}, "employment_income": {YEAR: fam["earnings"][j]}, "private_pension_income": {YEAR: private_pension * (j == 0)}, @@ -238,7 +258,10 @@ def situation(variants): if fam.get("dependant_age") is not None: j = len(fam["ages"]) name = f"p{i}_{j}" - people[name] = {"age": {YEAR: fam["dependant_age"]}} + people[name] = { + "age": {YEAR: fam["dependant_age"]}, + "is_claimant_or_partner": {YEAR: False}, + } for variable, value in extra.get(j, {}).items(): people[name][variable] = {YEAR: value} names.append(name) @@ -421,6 +444,9 @@ def test_war_pensions_never_count(data): st.integers(16, 19), ) @example([GENERAL_RULES_PENSIONER, KINGSTON_COUPLE], "iidb", 17) +# Found by CI: a dependant of 17 is under 16 years younger than a claimant of +# 32, so without the declared roles the model read them as a partner. +@example([YOUNG_PARENT], "iidb", 17) def test_dependants_benefits_never_count(families, change, dependant_age): families = [{**fam, "dependant_age": dependant_age} for fam in families] variants = [] From f12016368d0552886477dd1f20b647fac4681fb0 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 05:19:23 -0400 Subject: [PATCH 09/10] Pin the CI counterexample at the dependant age CI drew (19) Review of 1ae527d7c noted the failing CI draw had a dependant of 19; at 17 the model already reads them as a child. Correct the example and comment. Part of #2510. Co-Authored-By: Claude Opus 5.5 --- ...st_council_tax_reduction_prescribed_benefits_properties.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py b/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py index 6e1541fa8c..3bfc7883ef 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_prescribed_benefits_properties.py @@ -444,9 +444,9 @@ def test_war_pensions_never_count(data): st.integers(16, 19), ) @example([GENERAL_RULES_PENSIONER, KINGSTON_COUPLE], "iidb", 17) -# Found by CI: a dependant of 17 is under 16 years younger than a claimant of +# Found by CI: a dependant of 19 is under 16 years younger than a claimant of # 32, so without the declared roles the model read them as a partner. -@example([YOUNG_PARENT], "iidb", 17) +@example([YOUNG_PARENT], "iidb", 19) def test_dependants_benefits_never_count(families, change, dependant_age): families = [{**fam, "dependant_age": dependant_age} for fam in families] variants = [] From d32849ce5564069e429c4b6a1f9d22d4a40567cf Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 20 Oct 2026 18:31:20 -0400 Subject: [PATCH 10/10] Test a head who applies alone with her own State Pension and IIDB Under the merged code the head's State Pension counts through #2078's member mask and her IIDB through this PR's applicant-and-partner mask, while the father's IIDB in her benefit unit does not count. The case fails on each parent: 2,432.56 on f12016368 and 12,547.60 on main 76cfc9e79, against 14,980.16. Co-Authored-By: Claude Opus 5.5 --- ...cil_tax_reduction_prescribed_benefits.yaml | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml index 458720e4e5..ea63047128 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_prescribed_benefits.yaml @@ -519,6 +519,62 @@ council_tax_reduction_head_applies_alone: true council_tax_reduction_applicable_income: 116.95 * 52 +- name: Where a household head applies alone, her own IIDB counts with her State Pension, and the father's does not + # Only the merged code counts both: the head's State Pension through the + # applicant's member mask, her IIDB through the applicant-and-partner mask. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + grandmother: + age: 80 + is_household_head: true + state_pension: 241.30 * 52 + iidb_reported: 46.78 * 52 + mother: + age: 45 + is_parent: true + is_household_head: false + father: + age: 46 + is_parent: true + is_household_head: false + iidb_reported: 233.90 * 52 + child: + age: 10 + is_household_head: false + benunits: + family: + members: [grandmother, mother, father, child] + would_claim_uc: false + would_claim_pc: false + would_claim_child_benefit: false + households: + home: + members: [grandmother, mother, father, child] + country: ENGLAND + region: SOUTH_EAST + local_authority: MAIDSTONE + council_tax: 1_800 + savings: 0 + output: + council_tax_reduction_head_applies_alone: true + is_council_tax_reduction_applicant_or_partner: [true, false, false, false] + council_tax_reduction_pensioner: true + # State Pension 12,547.60, below the 12,570 personal allowance. + income_tax: [0, 0, 0, 0] + # State Pension 241.30 * 52 = 12,547.60, plus her IIDB at 20% + # 46.78 * 52 = 2,432.56: 14,980.16. The father's 12,162.80 IIDB is not + # hers (SI 2012/2885 Sch 1 para 11). + council_tax_reduction_applicable_income: 14_980.16 + # A single pensioner who reached pensionable age before April 2021: + # 256.00 * 52 = 13,312.00. + council_tax_reduction_applicable_amount: 13_312 + # 1,800 - 0.2 x (14,980.16 - 13,312.00) = 1,800 - 333.632 = 1,466.368. + # The couple are in her benefit unit, so the model charges no + # non-dependant deduction for them (#2017). + council_tax_benefit: 1_466.368 + - name: A guarantee credit recipient with IIDB has nil Council Tax Reduction income period: 2026 absolute_error_margin: 0.01