diff --git a/changelog.d/ctr-pensioner-earnings-disregards.fixed.md b/changelog.d/ctr-pensioner-earnings-disregards.fixed.md new file mode 100644 index 0000000000..ee352c3764 --- /dev/null +++ b/changelog.d/ctr-pensioner-earnings-disregards.fixed.md @@ -0,0 +1 @@ +- Apply the pensioner Council Tax Reduction earnings disregards in England (SI 2012/2885 Sch 4), Wales (WSI 2013/3029 Sch 3) and Scotland (SSI 2012/319 Sch 2). A pension-age family's net earnings are now reduced by £25 a week for a lone parent, £10 for a couple and £5 otherwise, capped at net earnings, plus the £17.10 additional earnings disregard where a work condition is met and net earnings cover it (£37.10 in Scotland from 6 April 2020 to 4 April 2021). These sums are fixed in law and are not uprated. Before, pensioner CTR disregarded no earnings. diff --git a/policyengine_uk/parameters/gov/local_authorities/england/council_tax_reduction/pensioners/earnings_disregard.yaml b/policyengine_uk/parameters/gov/local_authorities/england/council_tax_reduction/pensioners/earnings_disregard.yaml new file mode 100644 index 0000000000..9b25143c68 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/england/council_tax_reduction/pensioners/earnings_disregard.yaml @@ -0,0 +1,47 @@ +description: Weekly sums the English Council Tax Reduction prescribed requirements disregard from the net earnings of a pensioner applicant and partner. +single: + description: Weekly earnings disregard for a pensioner applicant who has no partner and is not a lone parent, where no £20 disregard applies. + values: + 2013-04-01: 5 + metadata: + label: England pensioner CTR earnings disregard, single + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction Schemes (Prescribed Requirements) (England) Regulations 2012 Sch. 4 para. 8(a) + href: https://www.legislation.gov.uk/uksi/2012/2885/schedule/4/paragraph/8 +couple: + description: Weekly earnings disregard for a pensioner applicant who has a partner, where no £20 disregard applies. + values: + 2013-04-01: 10 + metadata: + label: England pensioner CTR earnings disregard, couple + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction Schemes (Prescribed Requirements) (England) Regulations 2012 Sch. 4 para. 8(b) + href: https://www.legislation.gov.uk/uksi/2012/2885/schedule/4/paragraph/8 +lone_parent: + description: Weekly earnings disregard for a pensioner applicant who is a lone parent. + values: + 2013-04-01: 25 + metadata: + label: England pensioner CTR earnings disregard, lone parent + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction Schemes (Prescribed Requirements) (England) Regulations 2012 Sch. 4 paras. 1(a) and 2 + href: https://www.legislation.gov.uk/uksi/2012/2885/schedule/4 +additional: + description: Weekly increase in the earnings disregard (the additional earnings disregard) where the pensioner applicant meets one of its work conditions and their net earnings at least equal the other disregards, the deductible childcare charges and this amount. + values: + 2013-04-01: 17.1 + metadata: + label: England pensioner CTR additional earnings disregard + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction Schemes (Prescribed Requirements) (England) Regulations 2012 Sch. 4 para. 10(1) and (3)(c) + href: https://www.legislation.gov.uk/uksi/2012/2885/schedule/4/paragraph/10 +metadata: + label: England pensioner CTR earnings disregards diff --git a/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/pensioners/earnings_disregard.yaml b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/pensioners/earnings_disregard.yaml new file mode 100644 index 0000000000..e3fc2ed14f --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/scotland/council_tax_reduction/pensioners/earnings_disregard.yaml @@ -0,0 +1,56 @@ +description: Weekly sums the Scottish Council Tax Reduction (State Pension Credit) Regulations disregard from the net earnings of an applicant who has attained the qualifying age for state pension credit, and their partner. +single: + description: Weekly earnings disregard for a pension-age applicant who has no partner and is not a lone parent, where no £20 disregard applies. + values: + 2013-04-01: 5 + metadata: + label: Scotland pension-age CTR earnings disregard, single + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction (State Pension Credit) (Scotland) Regulations 2012 Sch. 2 para. 8(a) + href: https://www.legislation.gov.uk/ssi/2012/319/schedule/2/paragraph/8 +couple: + description: Weekly earnings disregard for a pension-age applicant who has a partner, where no £20 disregard applies. + values: + 2013-04-01: 10 + metadata: + label: Scotland pension-age CTR earnings disregard, couple + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction (State Pension Credit) (Scotland) Regulations 2012 Sch. 2 para. 8(b) + href: https://www.legislation.gov.uk/ssi/2012/319/schedule/2/paragraph/8 +lone_parent: + description: Weekly earnings disregard for a pension-age applicant who is a lone parent. + values: + 2013-04-01: 25 + metadata: + label: Scotland pension-age CTR earnings disregard, lone parent + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction (State Pension Credit) (Scotland) Regulations 2012 Sch. 2 paras. 1(a) and 2 + href: https://www.legislation.gov.uk/ssi/2012/319/schedule/2 +additional: + description: Weekly increase in the earnings disregard (the additional earnings disregard) where the pension-age applicant meets one of its work conditions and their net earnings at least equal the other disregards, the deductible childcare charges and this amount. + values: + 2013-04-01: 17.1 + # The Council Tax Reduction (Scotland) Amendment (No. 3) (Coronavirus) + # Regulations 2020 (SSI 2020/108) reg. 3(a), in force 6 April 2020, + # substituted "£37.10 until and including 4 April 2021, and after that + # by £17.10" in para. 10(1), and the same words without "by" in para. + # 10(3)(c). + 2020-04-06: 37.1 + 2021-04-05: 17.1 + metadata: + label: Scotland pension-age CTR additional earnings disregard + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction (State Pension Credit) (Scotland) Regulations 2012 Sch. 2 para. 10(1) and (3)(c) + href: https://www.legislation.gov.uk/ssi/2012/319/schedule/2/paragraph/10 + - title: The Council Tax Reduction (Scotland) Amendment (No. 3) (Coronavirus) Regulations 2020 reg. 3(a) + href: https://www.legislation.gov.uk/ssi/2020/108/regulation/3 +metadata: + label: Scotland pension-age CTR earnings disregards diff --git a/policyengine_uk/parameters/gov/local_authorities/wales/council_tax_reduction/pensioners/earnings_disregard.yaml b/policyengine_uk/parameters/gov/local_authorities/wales/council_tax_reduction/pensioners/earnings_disregard.yaml new file mode 100644 index 0000000000..ef67278ac0 --- /dev/null +++ b/policyengine_uk/parameters/gov/local_authorities/wales/council_tax_reduction/pensioners/earnings_disregard.yaml @@ -0,0 +1,55 @@ +description: Weekly sums the Welsh Council Tax Reduction prescribed requirements disregard from the net earnings of a pensioner applicant and partner. +single: + description: Weekly earnings disregard for a pensioner applicant who has no partner and is not a lone parent, where no £20 disregard applies. + values: + 2013-04-01: 5 + metadata: + label: Wales pensioner CTR earnings disregard, single + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2013 Sch. 3 para. 8(a) + href: https://www.legislation.gov.uk/wsi/2013/3029/schedule/3/paragraph/8 + - title: The Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2012 Sch. 3 para. 8(a) (financial year 2013-14) + href: https://www.legislation.gov.uk/wsi/2012/3144/schedule/3/made +couple: + description: Weekly earnings disregard for a pensioner applicant who has a partner, where no £20 disregard applies. + values: + 2013-04-01: 10 + metadata: + label: Wales pensioner CTR earnings disregard, couple + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2013 Sch. 3 para. 8(b) + href: https://www.legislation.gov.uk/wsi/2013/3029/schedule/3/paragraph/8 + - title: The Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2012 Sch. 3 para. 8(b) (financial year 2013-14) + href: https://www.legislation.gov.uk/wsi/2012/3144/schedule/3/made +lone_parent: + description: Weekly earnings disregard for a pensioner applicant who is a lone parent. + values: + 2013-04-01: 25 + metadata: + label: Wales pensioner CTR earnings disregard, lone parent + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2013 Sch. 3 paras. 1(a) and 2 + href: https://www.legislation.gov.uk/wsi/2013/3029/schedule/3 + - title: The Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2012 Sch. 3 paras. 1(a) and 2 (financial year 2013-14) + href: https://www.legislation.gov.uk/wsi/2012/3144/schedule/3/made +additional: + description: Weekly increase in the earnings disregard (the additional earnings disregard) where the pensioner applicant meets one of its work conditions and their net earnings at least equal the other disregards, the deductible childcare charges and this amount. + values: + 2013-04-01: 17.1 + metadata: + label: Wales pensioner CTR additional earnings disregard + unit: currency-GBP + period: week + reference: + - title: The Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2013 Sch. 3 para. 10(1) and (3)(c) + href: https://www.legislation.gov.uk/wsi/2013/3029/schedule/3/paragraph/10 + - title: The Council Tax Reduction Schemes and Prescribed Requirements (Wales) Regulations 2012 Sch. 3 para. 10(1) and (3)(c) (financial year 2013-14) + href: https://www.legislation.gov.uk/wsi/2012/3144/schedule/3/made +metadata: + label: Wales pensioner CTR earnings disregards diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner_earnings_disregard.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner_earnings_disregard.yaml new file mode 100644 index 0000000000..c077000081 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner_earnings_disregard.yaml @@ -0,0 +1,454 @@ +# Pensioner Council Tax Reduction earnings disregards. +# +# SI 2012/2885 (England) Sch 4, WSI 2013/3029 (Wales) Sch 3 and SSI 2012/319 +# (Scotland) Sch 2 disregard from a pension-age applicant's and partner's net +# earnings £25 a week for a lone parent (para 2), otherwise £5 with no partner +# or £10 with a partner (para 8), plus £17.10 (para 10) where a work condition +# is met and net earnings at least equal the other disregards, the deductible +# childcare charges and £17.10. Scotland read £37.10 for £17.10 from 6 April +# 2020 to 4 April 2021 (SSI 2020/108 reg 3(a)); England and Wales did not. +# +# Every person here is aged 70 (pension age, no employee National Insurance) +# unless stated, and earns less than the personal allowance, so net earnings +# equal gross earnings unless stated. Annual figures are weekly x 52. + +- name: England pensioner without earnings has no earnings disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + state_pension: 15_000 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: ENGLAND + output: + council_tax_reduction_pensioner_earnings_disregard: 0 + +- name: England single pensioner earning 1,000 a year has 5 a week disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 1_000 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: ENGLAND + output: + housing_benefit_net_earnings: 1_000 + # 5 x 52. + council_tax_reduction_pensioner_earnings_disregard: 260 + +- name: England single pensioner earning 3 a week has all 3 disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 156 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: ENGLAND + output: + # min(260, 3 x 52 = 156). + council_tax_reduction_pensioner_earnings_disregard: 156 + +- name: England pensioner couple has 10 a week disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 70 + employment_income: 2_000 + partner: + age: 70 + benunits: + benunit: + members: [head, partner] + households: + household: + members: [head, partner] + country: ENGLAND + output: + # 10 x 52. + council_tax_reduction_pensioner_earnings_disregard: 520 + +- name: England pensioner couple earning 3 and 4 a week has both disregarded + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 70 + employment_income: 156 + partner: + age: 70 + employment_income: 208 + benunits: + benunit: + members: [head, partner] + households: + household: + members: [head, partner] + country: ENGLAND + output: + # min(520, 156 + 208 = 364). + council_tax_reduction_pensioner_earnings_disregard: 364 + +- name: England pensioner lone parent has 25 a week disregarded, not 25 plus 5 + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 70 + employment_income: 2_000 + weekly_hours: 10 + child: + age: 10 + benunits: + benunit: + members: [parent, child] + households: + household: + members: [parent, child] + country: ENGLAND + output: + # 25 x 52. Para 8's 5 is not added (as for Housing Benefit, DWP HB + # guidance BP2 para P2.159). 10 hours meets no work condition. + council_tax_reduction_pensioner_earnings_disregard: 1_300 + +- name: England pensioner working 30 hours gets the additional 17.10 a week + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 10_000 + weekly_hours: 30 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: ENGLAND + output: + meets_housing_benefit_additional_earnings_disregard_conditions: true + # (5 + 17.10) x 52 = 260 + 889.20; net earnings 10,000 >= 1,149.20. + council_tax_reduction_pensioner_earnings_disregard: 1_149.20 + +- name: England pensioner working 29 hours does not get the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 10_000 + weekly_hours: 29 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: ENGLAND + output: + meets_housing_benefit_additional_earnings_disregard_conditions: false + council_tax_reduction_pensioner_earnings_disregard: 260 + +- name: Net earnings exactly equal to the disregards and 17.10 meet the earnings test + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 1_149.20 + weekly_hours: 30 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: ENGLAND + output: + # Para 10(1): "equal or exceed" 260 + 0 childcare + 889.20 = 1,149.20. + council_tax_reduction_pensioner_earnings_disregard: 1_149.20 + +- name: Net earnings a penny short of the disregards and 17.10 fail the earnings test + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 1_149.19 + weekly_hours: 30 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: ENGLAND + output: + council_tax_reduction_pensioner_earnings_disregard: 260 + +- name: England disabled pensioner working 20 hours gets the additional disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 10_000 + weekly_hours: 20 + is_disabled_for_benefits: true + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: ENGLAND + output: + # Para 10(2)(b)(iv): 16 hours and para 5(1) satisfied. The model gives + # 5 + 17.10 = 1,149.20 a year; the law gives the para 5(1) 20 in place of + # the 5, so 1,929.20. The 20 disregards are a known limitation. + council_tax_reduction_pensioner_earnings_disregard: 1_149.20 + +- name: England pensioner couple, one working 30 hours, gets 10 plus 17.10 + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 70 + employment_income: 10_000 + weekly_hours: 30 + partner: + age: 68 + benunits: + benunit: + members: [head, partner] + households: + household: + members: [head, partner] + country: ENGLAND + output: + # (10 + 17.10) x 52 = 520 + 889.20. + council_tax_reduction_pensioner_earnings_disregard: 1_409.20 + +- name: Wales pensioner working 30 hours gets 5 plus 17.10 + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 10_000 + weekly_hours: 30 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: WALES + output: + council_tax_reduction_pensioner_earnings_disregard: 1_149.20 + +- name: Scotland pensioner working 30 hours gets 5 plus 17.10 in 2026 + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 10_000 + weekly_hours: 30 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: SCOTLAND + output: + council_tax_reduction_pensioner_earnings_disregard: 1_149.20 + +- name: Scotland pensioner working 30 hours gets 5 plus 37.10 in 2020 + period: 2020 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 10_000 + weekly_hours: 30 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: SCOTLAND + output: + # SSI 2020/108: (5 + 37.10) x 52 = 260 + 1,929.20. + council_tax_reduction_pensioner_earnings_disregard: 2_189.20 + +- name: Scotland pensioner working 30 hours is back to 5 plus 17.10 in 2021 + period: 2021 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 10_000 + weekly_hours: 30 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: SCOTLAND + output: + council_tax_reduction_pensioner_earnings_disregard: 1_149.20 + +- name: England pensioner working 30 hours gets 5 plus 17.10 in 2020 + period: 2020 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 10_000 + weekly_hours: 30 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: ENGLAND + output: + # No coronavirus increase in the English prescribed requirements. + council_tax_reduction_pensioner_earnings_disregard: 1_149.20 + +- name: Scottish working-age applicant has no pensioner earnings disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + worker: + age: 40 + employment_income: 10_000 + weekly_hours: 35 + benunits: + benunit: + members: [worker] + households: + household: + members: [worker] + country: SCOTLAND + output: + council_tax_reduction_pensioner_earnings_disregard: 0 + +- name: A pension-age claimant whose partner is on income-related ESA is not a CTR pensioner + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 70 + employment_income: 1_000 + partner: + age: 50 + benunits: + benunit: + members: [claimant, partner] + esa_income: 4_000 + households: + household: + members: [claimant, partner] + country: ENGLAND + output: + # SI 2012/2885 reg 3(2): not a pensioner where the applicant or partner + # is on income-related ESA, so the pensioner schedule does not apply. + council_tax_reduction_pensioner: false + council_tax_reduction_pensioner_earnings_disregard: 0 + +- name: Northern Ireland pensioner has no Council Tax Reduction earnings disregard + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + employment_income: 1_000 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + country: NORTHERN_IRELAND + output: + council_tax_reduction_pensioner_earnings_disregard: 0 + +- name: England pensioner with State Pension and earnings has 5 a week of earnings disregarded in CTR + period: 2026 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + state_pension: 15_000 + employment_income: 1_000 + benunits: + benunit: + members: [pensioner] + claims_all_entitled_benefits: true + households: + household: + members: [pensioner] + country: ENGLAND + local_authority: MAIDSTONE + tenure_type: OWNED_OUTRIGHT + council_tax: 2_000 + savings: 0 + output: + # (15,000 + 1,000 - 12,570) x 20%. + income_tax: 686 + guarantee_credit: 0 + in_receipt_of_savings_credit_only: false + # 1,000 - 686 x 1,000 / 16,000. + housing_benefit_net_earnings: 957.13 + council_tax_reduction_pensioner_earnings_disregard: 260 + # 15,000 + 1,000 - 686 - 260 (main: 15,314). + council_tax_reduction_applicable_income: 15_054 + council_tax_reduction_applicable_amount: 13_312 + # 2,000 - 20% x (15,054 - 13,312) (main: 1,599.60). + council_tax_reduction: 1_651.60 diff --git a/policyengine_uk/tests/test_council_tax_reduction_pension_credit_properties.py b/policyengine_uk/tests/test_council_tax_reduction_pension_credit_properties.py index e32f8363e7..4b9e0b176c 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_pension_credit_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_pension_credit_properties.py @@ -23,8 +23,10 @@ nothing otherwise. 4. Differential: everyone else keeps the previous formula, max(0, L - 0.2 x max(0, I - A) - ND) when household savings are within 16,000, where I is the - income definition on main before this change, recomputed here from its - components rather than read from the variable under test. + income definition on main before this change less the schemes' pensioner + earnings disregard, both recomputed here (the income from its components, + the disregard from the statutory sums) rather than read from the variables + under test. 5. Metamorphic: a guarantee credit recipient's CTR does not change when the State Pension changes. 6. Metamorphic: nor when its savings change. @@ -260,7 +262,8 @@ def situation( ] # council_tax_reduction_applicable_income on main before this change (1c5b4d04): # these incomes and benefits, less income tax, National Insurance and half of -# pension contributions, floored at zero. +# pension contributions, floored at zero. Since #2033, a pensioner family's +# earnings disregard also comes off before the floor. MAIN_INCOME_COMPONENTS = [ "employment_income", "self_employment_income", @@ -282,6 +285,12 @@ def situation( "universal_credit", ] MAIN_DEDUCTIONS = ["income_tax", "national_insurance"] +# The pensioner earnings disregards in 2026, written out from the law, not +# read from the parameters: £25 a week for a lone parent, £10 with a partner, +# £5 otherwise, plus £17.10 (SI 2012/2885 Sch 4 paras 2, 8 and 10; WSI +# 2013/3029 Sch 3; SSI 2012/319 Sch 2). +PENSIONER_FLAT_DISREGARD = {"lone_parent": 25, "couple": 10, "single": 5} +PENSIONER_ADDITIONAL_DISREGARD = 17.10 HOUSEHOLD_VARIABLES = [ "council_tax_reduction_maximum_eligible_liability", "council_tax_reduction_household_has_pensioner", @@ -300,6 +309,40 @@ def guarantee_reform(single_weekly): } +def pensioner_earnings_disregard(sim, claimants): + """The schemes' pensioner earnings disregard from the statutory sums. + + Net earnings and the work conditions are Housing Benefit's (tested with + #1908); the amounts, the cap at net earnings and the "equal or exceed" + earnings test are recomputed here. Zero for families that are not the + schemes' pensioners. + """ + + def read(variable): + return np.asarray(sim.calculate(variable, YEAR))[claimants] + + net = read("housing_benefit_net_earnings").astype(float) + lone_parent = read("is_lone_parent").astype(bool) + couple = read("is_couple").astype(bool) + weekly = np.where( + lone_parent, + PENSIONER_FLAT_DISREGARD["lone_parent"], + np.where( + couple, + PENSIONER_FLAT_DISREGARD["couple"], + PENSIONER_FLAT_DISREGARD["single"], + ), + ) + standard = np.minimum(weekly * 52, net) + additional = PENSIONER_ADDITIONAL_DISREGARD * 52 + covers = np.round(net, 2) >= np.round(standard + additional, 2) + conditions = read( + "meets_housing_benefit_additional_earnings_disregard_conditions" + ).astype(bool) + disregard = standard + np.where(conditions & covers, additional, 0) + return np.where(read("council_tax_reduction_pensioner"), disregard, 0) + + def calculate(units, guarantee=None, **kwargs): data, claimants = situation(units, **kwargs) sim = Simulation(situation=data, reform=guarantee_reform(guarantee)) @@ -320,7 +363,8 @@ def benunit_total(names): 0, benunit_total(MAIN_INCOME_COMPONENTS) - benunit_total(MAIN_DEDUCTIONS) - - 0.5 * benunit_total(["pension_contributions"]), + - 0.5 * benunit_total(["pension_contributions"]) + - pensioner_earnings_disregard(sim, claimants), ) values["national"] = np.array( [ diff --git a/policyengine_uk/tests/test_council_tax_reduction_pensioner_earnings_disregard_properties.py b/policyengine_uk/tests/test_council_tax_reduction_pensioner_earnings_disregard_properties.py new file mode 100644 index 0000000000..34b3c49c58 --- /dev/null +++ b/policyengine_uk/tests/test_council_tax_reduction_pensioner_earnings_disregard_properties.py @@ -0,0 +1,313 @@ +"""Property-based tests for the pensioner Council Tax Reduction earnings disregards. + +SI 2012/2885 Sch 4 (England), WSI 2013/3029 Sch 3 (Wales) and SSI 2012/319 +Sch 2 (Scotland) disregard from a pension-age applicant's and partner's net +earnings £25 a week for a lone parent, £10 for a couple and £5 for anyone +else, plus £17.10 where a work condition is met and net earnings at least +equal the other disregards, the deductible childcare charges and £17.10. +Scotland read £37.10 for £17.10 from 6 April 2020 to 4 April 2021 (SSI +2020/108). The sums and the work conditions the model applies correspond to +the Housing Benefit pension-age schedule (SI 2006/214 Sch 4). + +Invariants, for any generated population of families: + +1. Bounds: 0 <= disregard <= min(net earnings, (£25 + £37.10) x 52); no + earnings, no disregard; zero for a family that is not one of the schemes' + pensioners (whatever its ages), and zero in Northern Ireland or where the + country is unknown. +2. The statutory sums: in every year from 2015 (the first year the model + simulates) to 2030 and in each nation, a pension-age family with enough + earnings and no work condition has exactly its weekly sum x 52 + disregarded, £5, £10 or £25; with a work condition, that plus £17.10 x 52 + (£37.10 in Scotland in 2020). The parameters themselves hold those sums, + without uprating, in every year from 2013-14, when the schemes began. +3. Metamorphic: the disregard is non-decreasing in employment income. +4. The disregard never raises Council Tax Reduction applicable income, lowers + it by no more than the claimant's and partner's gross earnings, and never + lowers the award. +5. Differential against Housing Benefit: for pension-age families without + childcare charges, the CTR disregard equals the Housing Benefit disregard + in every nation and year, except England and Wales in 2020, where Housing + Benefit's additional sum was £37.10 (SI 2020/371 reg 5) and CTR's stayed + £17.10 (an intended difference, which the test pins). +6. The Housing Benefit disregard (#1908) is the statutory weekly sum x 52 in + every year from 2015 to 2030, at working and pension age: £5, £10 or £25 + (SI 2006/213 Sch 4 paras 4, 7, 10; SI 2006/214 Sch 4 paras 2, 7), plus + £17.10 (£37.10 in 2020, SI 2020/371 reg 5) where a work condition holds, + for families with enough earnings and no Income Support, income-based JSA + or income-related ESA (whose working-age earnings are all disregarded). +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.system import system + +WEEKS = 52 +PROPERTY_SETTINGS = settings( + max_examples=5, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +NATIONS = ["ENGLAND", "WALES", "SCOTLAND"] +SHAPES = { + "single": (1, 0), + "couple": (2, 0), + "lone_parent": (1, 2), + "couple_with_children": (2, 1), +} +WEEKLY = {"single": 5, "couple": 10, "lone_parent": 25, "couple_with_children": 10} +# State Pension age rises from 66 to 67 between 2026 and 2028, so 68 and over +# is pension age in every year tested and 60 and under is working age. +PENSION_AGE = st.integers(68, 90) +ADULT_AGE = st.one_of(st.integers(18, 90), st.sampled_from([24, 25, 60, 68])) +HOURS = st.one_of( + st.sampled_from([0.0, 15.0, 16.0, 29.0, 30.0]), st.floats(0, 50, allow_nan=False) +) +EARNINGS = st.one_of( + st.just(0.0), + st.floats(0, 3_000, allow_nan=False), + st.floats(0, 60_000, allow_nan=False), +) +MAX_ANNUAL = (25 + 37.1) * WEEKS + + +def additional_weekly(nation, year): + return 37.1 if (nation == "SCOTLAND" and year == 2020) else 17.1 + + +@st.composite +def adults(draw, age, earnings): + return dict( + age=draw(age), + employment_income=draw(earnings), + weekly_hours=draw(HOURS), + disabled=draw(st.booleans()), + state_pension=draw(st.one_of(st.just(0.0), st.floats(0, 20_000))), + ) + + +@st.composite +def families(draw, age=ADULT_AGE, earnings=EARNINGS, nations=NATIONS): + shape = draw(st.sampled_from(sorted(SHAPES))) + n_adults, n_children = SHAPES[shape] + return dict( + shape=shape, + nation=draw(st.sampled_from(nations)), + adults=[draw(adults(age, earnings)) for _ in range(n_adults)], + children=[draw(st.integers(0, 15)) for _ in range(n_children)], + ) + + +def situation(units, year, earnings_bump=0.0, no_income_related_benefits=False): + people, benunits, households = {}, {}, {} + for i, unit in enumerate(units): + names = [] + for j, adult in enumerate(unit["adults"]): + name = f"a{i}_{j}" + people[name] = { + "age": {year: adult["age"]}, + "employment_income": { + year: adult["employment_income"] + (earnings_bump if j == 0 else 0) + }, + "weekly_hours": {year: adult["weekly_hours"]}, + "is_disabled_for_benefits": {year: adult["disabled"]}, + "state_pension": {year: adult["state_pension"]}, + } + names.append(name) + for k, child_age in enumerate(unit["children"]): + name = f"c{i}_{k}" + people[name] = {"age": {year: child_age}} + names.append(name) + benunits[f"b{i}"] = { + "members": names, + "claims_all_entitled_benefits": {year: True}, + } + if no_income_related_benefits: + for benefit in ("income_support", "jsa_income", "esa_income"): + benunits[f"b{i}"][benefit] = {year: 0.0} + households[f"h{i}"] = { + "members": names, + "country": {year: unit["nation"]}, + "council_tax": {year: 2_000.0}, + "savings": {year: 0.0}, + } + return {"people": people, "benunits": benunits, "households": households} + + +VARIABLES = [ + "council_tax_reduction_pensioner_earnings_disregard", + "housing_benefit_net_earnings", + "housing_benefit_applicable_income_disregard", + "meets_housing_benefit_additional_earnings_disregard_conditions", +] + + +def calculate(units, year=2026, variables=VARIABLES, **kwargs): + sim = Simulation(situation=situation(units, year, **kwargs)) + return {v: np.asarray(sim.calculate(v, year), dtype=float) for v in variables} + + +def gross_earnings(unit): + return sum(max(adult["employment_income"], 0) for adult in unit["adults"]) + + +def working_age(unit): + return all(adult["age"] <= 60 for adult in unit["adults"]) + + +@PROPERTY_SETTINGS +@given( + st.lists( + families(nations=NATIONS + ["NORTHERN_IRELAND", "UNKNOWN"]), + min_size=1, + max_size=30, + ) +) +def test_disregard_is_bounded_and_only_for_pension_age_families_in_great_britain( + units, +): + values = calculate(units, variables=VARIABLES + ["council_tax_reduction_pensioner"]) + disregard = values["council_tax_reduction_pensioner_earnings_disregard"] + net = values["housing_benefit_net_earnings"] + pensioner = values["council_tax_reduction_pensioner"].astype(bool) + for i, unit in enumerate(units): + assert disregard[i] >= 0, unit + assert disregard[i] <= min(net[i], MAX_ANNUAL) + 0.01, unit + if gross_earnings(unit) == 0: + assert disregard[i] == 0, unit + if working_age(unit): + assert not pensioner[i], unit + if not pensioner[i] or unit["nation"] in ("NORTHERN_IRELAND", "UNKNOWN"): + assert disregard[i] == 0, unit + + +@PROPERTY_SETTINGS +@given( + st.lists( + families(age=PENSION_AGE, earnings=st.floats(20_000, 40_000)), + min_size=1, + max_size=30, + ), + st.integers(2015, 2030), +) +def test_disregard_is_the_statutory_weekly_sum_in_every_year(units, year): + values = calculate(units, year=year) + disregard = values["council_tax_reduction_pensioner_earnings_disregard"] + conditions = values[ + "meets_housing_benefit_additional_earnings_disregard_conditions" + ].astype(bool) + for i, unit in enumerate(units): + weekly = WEEKLY[unit["shape"]] + if conditions[i]: + weekly += additional_weekly(unit["nation"], year) + assert abs(disregard[i] - weekly * WEEKS) < 0.01, (unit, year) + + +def test_parameters_hold_the_statutory_sums_without_uprating(): + for year in range(2013, 2031): + # Read in each financial year (30 April, the model's convention for + # years; an integer year before 2015 reads 1 January instead). + p = system.parameters(f"{year}-04-30").gov.local_authorities + for nation in NATIONS: + sums = getattr( + p, nation.lower() + ).council_tax_reduction.pensioners.earnings_disregard + assert (sums.single, sums.couple, sums.lone_parent) == (5, 10, 25), ( + nation, + year, + ) + assert sums.additional == additional_weekly(nation, year), (nation, year) + + +@PROPERTY_SETTINGS +@given( + st.lists(families(), min_size=1, max_size=30), + st.floats(1, 5_000, allow_nan=False, allow_infinity=False), +) +def test_disregard_is_non_decreasing_in_earnings(units, bump): + key = "council_tax_reduction_pensioner_earnings_disregard" + before = calculate(units)[key] + after = calculate(units, earnings_bump=bump)[key] + for i, unit in enumerate(units): + assert after[i] >= before[i] - 0.01, (unit, bump) + + +@PROPERTY_SETTINGS +@given(st.lists(families(), min_size=1, max_size=30)) +def test_disregard_lowers_income_by_no_more_than_earnings_and_never_cuts_ctr(units): + keys = [ + "council_tax_reduction_applicable_income", + "simulated_council_tax_reduction_benunit", + ] + with_disregard = calculate(units, variables=keys) + sim = Simulation(situation=situation(units, 2026)) + sim.set_input( + "council_tax_reduction_pensioner_earnings_disregard", + 2026, + np.zeros(len(units)), + ) + without = {k: np.asarray(sim.calculate(k, 2026), dtype=float) for k in keys} + income, award = keys + for i, unit in enumerate(units): + assert with_disregard[income][i] <= without[income][i] + 0.01, unit + drop = without[income][i] - with_disregard[income][i] + assert drop <= gross_earnings(unit) + 0.01, unit + assert with_disregard[award][i] >= without[award][i] - 0.01, unit + + +@PROPERTY_SETTINGS +@given( + st.lists(families(age=PENSION_AGE), min_size=1, max_size=30), + st.sampled_from([2019, 2020, 2021, 2026]), +) +def test_differential_against_the_housing_benefit_pension_age_disregard(units, year): + values = calculate(units, year=year) + ctr = values["council_tax_reduction_pensioner_earnings_disregard"] + hb = values["housing_benefit_applicable_income_disregard"] + net = values["housing_benefit_net_earnings"] + conditions = values[ + "meets_housing_benefit_additional_earnings_disregard_conditions" + ].astype(bool) + for i, unit in enumerate(units): + if year == 2020 and unit["nation"] != "SCOTLAND": + # Intended difference: SI 2020/371 raised Housing Benefit's + # additional sum to £37.10; the English and Welsh CTR rules kept + # £17.10. Each still gives its own statutory amount. + flat = min(WEEKLY[unit["shape"]] * WEEKS, net[i]) + for value, additional in ((ctr[i], 17.1), (hb[i], 37.1)): + expected = flat + if conditions[i] and round(net[i], 2) >= round( + flat + additional * WEEKS, 2 + ): + expected += additional * WEEKS + assert abs(value - expected) < 0.01, (unit, year) + else: + assert abs(ctr[i] - hb[i]) < 0.01, (unit, year) + + +@PROPERTY_SETTINGS +@given( + st.lists( + families(earnings=st.floats(20_000, 40_000)), + min_size=1, + max_size=30, + ), + st.integers(2015, 2030), +) +def test_housing_benefit_disregard_is_the_statutory_weekly_sum_in_every_year( + units, year +): + values = calculate(units, year=year, no_income_related_benefits=True) + disregard = values["housing_benefit_applicable_income_disregard"] + conditions = values[ + "meets_housing_benefit_additional_earnings_disregard_conditions" + ].astype(bool) + for i, unit in enumerate(units): + weekly = WEEKLY[unit["shape"]] + if conditions[i]: + weekly += 37.1 if year == 2020 else 17.1 + assert abs(disregard[i] - weekly * WEEKS) < 0.01, (unit, year) 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 fc1e3ea7b4..e34a88f15c 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 @@ -11,8 +11,10 @@ class council_tax_reduction_applicable_income(Variable): "whose partner, is in receipt of Pension Credit guarantee credit, and " "use the Secretary of State's Pension Credit assessment of income, plus " "the savings credit payable, where the award is savings credit only. " - "The other adjustments those provisions allow (childcare charges, lone " - "parent and maintenance disregards, and the rest) are not modelled." + "Otherwise a pension-age family's net earnings are reduced by the " + "pensioner earnings disregards. The other adjustments those " + "provisions allow (childcare charges, the £20 earnings disregards, " + "maintenance disregards and the rest) are not modelled." ) definition_period = YEAR unit = GBP @@ -23,6 +25,9 @@ class council_tax_reduction_applicable_income(Variable): "https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/8", "https://www.legislation.gov.uk/ssi/2012/319/regulation/24", "https://www.legislation.gov.uk/ssi/2012/319/regulation/25", + "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", ] def formula(benunit, period, parameters): @@ -80,8 +85,15 @@ def formula(benunit, period, parameters): tax = add_for_members( benunit, period, ["income_tax", "national_insurance"], members ) + # SI 2012/2885 Sch 1 para 17(9) and Sch 4; WSI 2013/3029 Sch 1 para + # 11(9) and Sch 3; SSI 2012/319 reg 31(8) and Sch 2: the pensioner + # earnings disregards, capped at net earnings. Zero for working-age + # families. + earnings_disregard = benunit( + "council_tax_reduction_pensioner_earnings_disregard", period + ) income_under_general_rules = max_( - 0, increased_income - tax - pension_contributions + 0, increased_income - tax - pension_contributions - earnings_disregard ) # SI 2012/2885 Sch 1 para 13, WSI 2013/3029 Sch 1 para 7 and SSI # 2012/319 reg 24: a guarantee credit recipient's whole income is diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner_earnings_disregard.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner_earnings_disregard.py new file mode 100644 index 0000000000..17e26734c0 --- /dev/null +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/council_tax_reduction_pensioner_earnings_disregard.py @@ -0,0 +1,94 @@ +from policyengine_uk.model_api import * +from policyengine_uk.variables.household.demographic.country import Country + + +class council_tax_reduction_pensioner_earnings_disregard(Variable): + value_type = float + entity = BenUnit + label = "Council Tax Reduction pensioner earnings disregard" + documentation = ( + "Sums disregarded from the net earnings of a pension-age applicant and " + "partner under the national Council Tax Reduction rules for " + "pensioners in England, Wales and Scotland. A lone parent has £25 a " + "week, an applicant with a partner £10 and anyone else £5, capped at " + "net earnings. The additional earnings disregard of £17.10 (£37.10 " + "in Scotland from 6 April 2020 to 4 April 2021) is added where a work " + "condition is met and net earnings at least equal the other " + "disregards and the additional amount. The amounts and work " + "conditions are those of the Housing Benefit pension-age schedule " + "(SI 2006/214 Sch 4), and an employee's actual earnings have the same " + "deductions (SI 2012/2885 Sch 1 para 19, WSI 2013/3029 Sch 1 para " + "13, SSI 2012/319 reg 33), so the Housing Benefit net earnings and " + "work conditions are used. The notional tax on estimated earnings " + "and on self-employed profit can differ (Scotland deducts only the " + "personal allowance, SSI 2012/319 regs 33(4) and 37(1)); the model " + "applies the Housing Benefit approximation to all of them. The earnings test leaves out " + "childcare charges, which the model does not deduct from Council Tax " + "Reduction income. The £20 " + "disregards for disabled people, carers and some part-time " + "occupations, and the exempt work disregard, are not modelled. Zero " + "for families that are not the schemes' pensioners " + "(council_tax_reduction_pensioner), whose schemes are local in " + "England and have their own schedules in Wales and Scotland, and " + "outside Great Britain." + ) + definition_period = YEAR + unit = GBP + reference = ( + "https://www.legislation.gov.uk/uksi/2012/2885/schedule/4", + "https://www.legislation.gov.uk/uksi/2012/2885/schedule/1/paragraph/19", + "https://www.legislation.gov.uk/wsi/2013/3029/schedule/3", + "https://www.legislation.gov.uk/wsi/2013/3029/schedule/1/paragraph/13", + "https://www.legislation.gov.uk/ssi/2012/319/schedule/2", + "https://www.legislation.gov.uk/ssi/2012/319/regulation/33", + "https://www.legislation.gov.uk/uksi/2012/2885/regulation/3", + ) + + def formula(benunit, period, parameters): + p = parameters(period).gov.local_authorities + england = p.england.council_tax_reduction.pensioners.earnings_disregard + wales = p.wales.council_tax_reduction.pensioners.earnings_disregard + scotland = p.scotland.council_tax_reduction.pensioners.earnings_disregard + country = benunit.household("country", period) + in_england = country == Country.ENGLAND + in_wales = country == Country.WALES + in_scotland = country == Country.SCOTLAND + + def national(amount): + return select( + [in_wales, in_scotland], + [wales[amount], scotland[amount]], + default=england[amount], + ) + + net_earnings = benunit("housing_benefit_net_earnings", period) + # SI 2012/2885 Sch 4 paras 2 and 8; WSI 2013/3029 Sch 3 paras 2 and 8; + # SSI 2012/319 Sch 2 paras 2 and 8. A lone parent has £25 in all, + # not £25 plus para 8's £5, as for Housing Benefit. + weekly_amount = select( + [benunit("is_lone_parent", period), benunit("is_couple", period)], + [national("lone_parent"), national("couple")], + default=national("single"), + ) + standard = min_(weekly_amount * WEEKS_IN_YEAR, net_earnings) + # Para 10 in each: the additional earnings disregard. + additional_amount = national("additional") * WEEKS_IN_YEAR + # "Equal or exceed", compared in pence: net earnings are float32. + covers_additional = np.round(net_earnings.astype(float), 2) >= np.round( + (standard + additional_amount).astype(float), 2 + ) + additional = where( + benunit( + "meets_housing_benefit_additional_earnings_disregard_conditions", period + ) + & covers_additional, + additional_amount, + 0, + ) + # The pensioner schedules apply to the schemes' pensioners: the + # qualifying age for State Pension Credit, and no income-related + # benefit or Universal Credit award (SI 2012/2885 reg 3; WSI + # 2013/3029 reg 3; SSI 2012/319 reg 12). + pensioner = benunit("council_tax_reduction_pensioner", period) + in_great_britain = in_england | in_wales | in_scotland + return where(pensioner & in_great_britain, standard + additional, 0)