diff --git a/changelog.d/pension-credit-war-pensions.fixed.md b/changelog.d/pension-credit-war-pensions.fixed.md new file mode 100644 index 0000000000..865116033e --- /dev/null +++ b/changelog.d/pension-credit-war-pensions.fixed.md @@ -0,0 +1 @@ +- Count war disablement pensions, war widow's or widower's pensions and Armed Forces Compensation Scheme guaranteed income payments of the claimant and partner in Pension Credit income, each payment less £10 a week (State Pension Credit Regulations 2002 Sch. IV para. 1). Housing Benefit and Council Tax Reduction continue to disregard them in full, including where they take the Pension Credit assessment of a savings-credit-only award. diff --git a/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/income.yaml b/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/income.yaml index 3da4354bd1..67bf30db2b 100644 --- a/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/income.yaml +++ b/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/income.yaml @@ -14,6 +14,10 @@ values: 2002-01-01: - pension_credit_earnings - working_tax_credit + # War pensions (SPCA s.15(1)(g)) and AFCS guaranteed income payments + # (reg. 15(5)(aa)) of the claimant and partner, each payment less £10 a + # week (Sch. IV para. 1). + - pension_credit_war_pension_income - state_pension - private_pension_income - property_income_after_finance_costs diff --git a/policyengine_uk/parameters/gov/dwp/pension_credit/income/war_pension_disregard.yaml b/policyengine_uk/parameters/gov/dwp/pension_credit/income/war_pension_disregard.yaml new file mode 100644 index 0000000000..1f0a5066ac --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/pension_credit/income/war_pension_disregard.yaml @@ -0,0 +1,14 @@ +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 income for Pension Credit. +values: + 2003-10-06: 10 +metadata: + unit: currency-GBP + period: week + label: Pension Credit war pension disregard + reference: + - title: State Pension Credit Regulations 2002 Sch. IV para. 1 + href: https://www.legislation.gov.uk/uksi/2002/1792/schedule/IV/paragraph/1 + - title: State Pension Credit Regulations 2002 reg. 17(7) + href: https://www.legislation.gov.uk/uksi/2002/1792/regulation/17 + - title: State Pension Credit Regulations 2002 reg. 1(1) (in force 6 October 2003) + href: https://www.legislation.gov.uk/uksi/2002/1792/regulation/1 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_war_pension_income.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_war_pension_income.yaml new file mode 100644 index 0000000000..c81788d84c --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_war_pension_income.yaml @@ -0,0 +1,401 @@ +# War pensions and Armed Forces Compensation Scheme (AFCS) payments in +# Pension Credit income. +# +# SPCA 2002 s.15(1)(g) counts "a war disablement pension or war widow's or +# widower's pension"; SPC Regs 2002 reg 15(5)(aa) counts "a guaranteed income +# payment". Reg 17(7) applies Sch IV, whose para 1 disregards "£10 of any of +# the following": war disablement pensions, war widow's or widower's pensions +# and guaranteed income payments among them. PolicyEngine reads "£10 of any of +# the following" as £10 of each qualifying payment: afcs and +# war_widows_pension each get £10 a week, and an unused disregard does not +# pass to another payment or to the other member of a couple. The claimant's +# and the partner's payments count (SPCA s.5; reg 14: "any reference in this +# Part to the claimant shall apply equally to any partner of the claimant"). +# Reg 9 does not exclude these payments from Savings Credit qualifying income. +# +# Statutory 2026-27 rates (SI 2026/148 art 29): standard minimum guarantee +# £238.00 single and £363.25 couple (reg 6(1)); severe disability addition +# £86.05 (reg 6(5)(a)); Savings Credit threshold £208.07 single (reg 7(2)). +# Reg 7(1): maximum savings credit and amount A 60%, amount B 40%. The £10 +# disregard has been £10 since the Regulations were made. Payment amounts +# are illustrative weekly amounts (war pensions and AFCS payments are uprated +# by Ministry of Defence instruments, not by SI 2026/148). Annual amounts use +# the model's 52 weeks a year. + +- name: Case 1, an AFCS payment above £10 a week counts less £10. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + state_pension: 0 + afcs: 30 * 52 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + country: ENGLAND + output: + # (30 - 10) * 52 = 1,040. + pension_credit_war_pension_income: (30 - 10) * 52 + pension_credit_income: (30 - 10) * 52 + minimum_guarantee: 238 * 52 + # 238 * 52 - 1,040 = 12,376 - 1,040 = 11,336. + guarantee_credit: (238 - 20) * 52 + +- name: Case 2, an AFCS payment below £10 a week is wholly disregarded. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + state_pension: 0 + afcs: 5 * 52 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + country: ENGLAND + output: + # max(5 - 10, 0) * 52 = 0. + pension_credit_war_pension_income: 0 + pension_credit_income: 0 + guarantee_credit: 238 * 52 + +- name: Case 3, an AFCS payment of exactly £10 a week is wholly disregarded. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + state_pension: 0 + afcs: 10 * 52 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + country: ENGLAND + output: + # max(10 - 10, 0) * 52 = 0. + pension_credit_war_pension_income: 0 + pension_credit_income: 0 + guarantee_credit: 238 * 52 + +- name: Case 4, a war widow's pension counts less £10. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + state_pension: 0 + war_widows_pension: 30 * 52 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + country: ENGLAND + output: + # (30 - 10) * 52 = 1,040; 12,376 - 1,040 = 11,336. + pension_credit_war_pension_income: (30 - 10) * 52 + pension_credit_income: (30 - 10) * 52 + guarantee_credit: (238 - 20) * 52 + +- name: Case 5, one person with an AFCS payment and a war widow's pension gets £10 off each. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + state_pension: 0 + afcs: 20 * 52 + war_widows_pension: 15 * 52 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + country: ENGLAND + output: + # ((20 - 10) + (15 - 10)) * 52 = 15 * 52 = 780; 12,376 - 780 = 11,596. + pension_credit_war_pension_income: (10 + 5) * 52 + pension_credit_income: (10 + 5) * 52 + guarantee_credit: (238 - 15) * 52 + +- name: Case 6, each member of a couple gets their own £10. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 72 + state_pension: 0 + afcs: 30 * 52 + person2: + age: 70 + state_pension: 0 + war_widows_pension: 20 * 52 + benunits: + benunit: + members: [person1, person2] + households: + household: + members: [person1, person2] + country: ENGLAND + output: + # ((30 - 10) + (20 - 10)) * 52 = 30 * 52 = 1,560. + pension_credit_war_pension_income: (20 + 10) * 52 + pension_credit_income: (20 + 10) * 52 + minimum_guarantee: 363.25 * 52 + # 363.25 * 52 - 1,560 = 18,889 - 1,560 = 17,329. + guarantee_credit: (363.25 - 30) * 52 + +- name: Case 7, a partner's unused disregard does not pass to the claimant. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 72 + state_pension: 0 + afcs: 30 * 52 + person2: + age: 70 + state_pension: 0 + afcs: 5 * 52 + benunits: + benunit: + members: [person1, person2] + households: + household: + members: [person1, person2] + country: ENGLAND + output: + # max(30 - 10, 0) + max(5 - 10, 0) = 20 a week, not 30 + 5 - 20 = 15. + # 20 * 52 = 1,040; 18,889 - 1,040 = 17,849. + pension_credit_war_pension_income: 20 * 52 + pension_credit_income: 20 * 52 + guarantee_credit: (363.25 - 20) * 52 + +- name: Case 8, an AFCS payment is Savings Credit qualifying income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 80 + state_pension: 0 + private_pension_income: 198.07 * 52 + afcs: 40 * 52 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + country: ENGLAND + output: + # Private pension 10,299.64 is below the personal allowance and AFCS is + # not taxable income, so there is no income tax. + income_tax: 0 + # 198.07 + (40 - 10) = 228.07 a week; 228.07 * 52 = 11,859.64. + pension_credit_war_pension_income: (40 - 10) * 52 + pension_credit_income: 228.07 * 52 + savings_credit_income: 228.07 * 52 + # 238 - 228.07 = 9.93 a week; 9.93 * 52 = 516.36. + guarantee_credit: 9.93 * 52 + # Amount A = min(0.6 * (238 - 208.07), 0.6 * (228.07 - 208.07)) + # = min(17.958, 12.00) = 12.00; amount B = 0.4 * 0 = 0. + # 12.00 * 52 = 624. Whole pennies, so reg 7(4) rounding does not arise. + savings_credit: 12 * 52 + +- name: Case 9, a dependant's and a third adult's payments do not count. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 75 + state_pension: 0 + person2: + age: 72 + state_pension: 0 + person3: + age: 45 + afcs: 50 * 52 + person4: + age: 17 + afcs: 30 * 52 + benunits: + benunit: + members: [person1, person2, person3, person4] + households: + household: + members: [person1, person2, person3, person4] + country: ENGLAND + output: + is_claimant_or_partner: [true, true, false, false] + # Only the claimant's and partner's payments count (SPCA s.5; reg 14). + pension_credit_war_pension_income: 0 + pension_credit_income: 0 + +- name: Case 10, armed forces independence payment is not Pension Credit income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + state_pension: 0 + afcs: 30 * 52 + armed_forces_independence_payment: 100 * 52 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + country: ENGLAND + output: + # Reg 15(1)(ab) excepts armed forces independence payment; only the AFCS + # payment counts: (30 - 10) * 52 = 1,040. + pension_credit_war_pension_income: (30 - 10) * 52 + pension_credit_income: (30 - 10) * 52 + # Armed forces independence payment gives the severe disability + # addition (Sch I para 1; reg 6(5)(a)): (238 + 86.05) * 52 = 16,850.60. + minimum_guarantee: (238 + 86.05) * 52 + # 16,850.60 - 1,040 = 15,810.60. + guarantee_credit: (238 + 86.05 - 20) * 52 + +- name: Case 11, Bereavement Support Payment is still not Pension Credit income. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + state_pension: 0 + bsp: 30 * 52 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + country: ENGLAND + output: + # Reg 15(1)(n) excepts bereavement support payment. Unchanged behaviour: + # pension_credit_income and guarantee_credit are the same on main. + pension_credit_war_pension_income: 0 + pension_credit_income: 0 + guarantee_credit: 238 * 52 + +- name: Case 12, a reported war widow's pension is in the benefit totals and untaxed. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 40 + war_widows_pension_reported: 30 * 52 + benunits: + benunit: + members: [person1] + would_claim_uc: false + households: + household: + members: [person1] + country: ENGLAND + output: + war_widows_pension: 30 * 52 + # It joins the gross benefit totals that bsp is in, so moving FRS code 9 + # from bsp to this variable leaves the totals unchanged. + household_benefits: 30 * 52 + hbai_benefits: 30 * 52 + gov_spending: 30 * 52 + pre_budget_change_household_benefits: 30 * 52 + # ITEPA 2003 s.639: no income tax on war widow's pensions. + social_security_income: 0 + income_tax: 0 + +- name: Case 13, an unused disregard on one payment does not pass to the person's other payment. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 70 + state_pension: 0 + afcs: 5 * 52 + war_widows_pension: 30 * 52 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + country: ENGLAND + output: + # max(5 - 10, 0) + max(30 - 10, 0) = 20 a week, not 5 + 30 - 10 = 25. + # 20 * 52 = 1,040; 12,376 - 1,040 = 11,336. + pension_credit_war_pension_income: 20 * 52 + pension_credit_income: 20 * 52 + guarantee_credit: (238 - 20) * 52 + +- name: Case 14, savings-credit-only Housing Benefit and CTR do not count an AFCS payment. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + person1: + age: 80 + state_pension: 0 + private_pension_income: 240 * 52 + afcs: 30 * 52 + benunits: + benunit: + members: [person1] + households: + household: + members: [person1] + country: ENGLAND + region: NORTH_WEST + tenure_type: RENT_FROM_COUNCIL + rent: 6_000 + council_tax: 1_500 + output: + # The private pension (12,480) is below the 12,570 personal allowance and + # the AFCS payment is not taxable income, so there is no income tax. + income_tax: 0 + pension_credit_war_pension_income: (30 - 10) * 52 + # 12,480 + 1,040 = 13,520 (260.00 a week), above the 12,376 minimum + # guarantee. + pension_credit_income: 13_520 + guarantee_credit: 0 + # Amount A = min(0.6 * (238 - 208.07), 0.6 * (260 - 208.07)) = 17.958 a + # week; amount B = 0.4 * (260 - 238) = 8.80 a week, whole pennies so reg + # 7(4) rounding does not arise; savings credit (17.958 - 8.80) * 52 = + # 476.216, so the award is savings credit only. + savings_credit: 476.216 + # HB (SPC) Regs 2006 reg 27: the Secretary of State's assessment plus the + # savings credit payable. The model takes the authority to disregard war + # pensions in full (SSAA 1992 s.134(8); HB/CTB Guidance Manual P2.576), so + # the 1,040 counted for Pension Credit comes out: 13,520 - 1,040 + 476.216 + # = 12,956.216. There are no earnings, so no earnings disregard. + housing_benefit_applicable_income: 12_956.216 + # The CTR savings-credit-only route: its general route counts no war + # pension, so the same 1,040 comes out. + council_tax_reduction_applicable_income: 12_956.216 diff --git a/policyengine_uk/tests/test_pension_credit_war_pension_properties.py b/policyengine_uk/tests/test_pension_credit_war_pension_properties.py new file mode 100644 index 0000000000..122cfcb68e --- /dev/null +++ b/policyengine_uk/tests/test_pension_credit_war_pension_properties.py @@ -0,0 +1,316 @@ +"""War pensions and AFCS payments in Pension Credit income. + +State Pension Credit Act 2002 s.15(1)(g) counts "a war disablement pension or +war widow's or widower's pension", and the State Pension Credit Regulations +2002 reg 15(5)(aa) count "a guaranteed income payment" of the Armed Forces +Compensation Scheme. Reg 17(7) applies Sch IV, whose para 1 disregards "£10 of +any of the following". `pension_credit_war_pension_income` counts the +claimant's and partner's `afcs` and `war_widows_pension`, each payment less +£10 a week, and Pension Credit income includes it. + +Invariants, for single people and couples over State Pension age renting from +the council in 2026, with any mix of AFCS payments and war widow's pensions, +and with a carer receiving Carer's Allowance or Carer Support Payment in some +families (the sources #1952 added): + +1. Differential with Housing Benefit. Where neither Guarantee Credit nor + Savings Credit is due (with a savings-credit-only award Housing Benefit + uses the Pension Credit assessment plus the savings credit, HB (SPC) Regs + 2006 reg 27) and neither income floor binds, Housing Benefit applicable income plus its + income disregard equals Pension Credit income less the counted war pension + income. The difference is intended: the Housing Benefit (Persons who have + attained the qualifying age for state pension credit) Regulations 2006 reg + 29(1) count these payments and Sch 5 para 1 disregards £10 of them, but the + Social Security Administration Act 1992 s.134(8) lets an authority modify + its scheme to disregard "the whole or part" of a prescribed war + disablement or war widow's pension, and `housing_benefit_applicable_income` + counts neither `afcs` nor `war_widows_pension` (on the savings-credit-only + route it removes the war pension income the Pension Credit assessment + counts; see invariant 5). That is an assumption that + the authority runs such a local scheme with a full disregard; it is not a + statement about any particular authority. The draws have no earnings, no + pension contributions and capital of at most £10,000, so neither tariff + income nor the earnings rules apply. +2. Bounds and closed form. The counted amount is between 0 and the gross + payments of the claimant and partner, and equals the sum over the + claimant's and partner's payments of max(0, payment - £520 a year). +3. Monotone. Guarantee Credit does not rise as one person's AFCS payment + rises, and does not change while that payment is at or below £10 a week, + whatever war widow's pension the person also receives. +4. Metamorphic. Pension Credit income and Guarantee Credit depend on the + claimant's and partner's payments only as a collection: reassigning the + amounts between the two sources and between the claimant and partner + changes neither, because each payment has its own £10 a week. +5. Housing Benefit and Council Tax Reduction disregard war pensions in full on + every route. Comparing each family with and without its payments, where + neither run pays Guarantee Credit, both runs take the same route + (savings-credit-only or general) and neither income floor binds, Housing + Benefit applicable income and Council Tax Reduction applicable income + change by exactly the change in Pension Credit paid: on the + savings-credit-only route they add the savings credit payable (HB (SPC) + Regs 2006 reg 27), which the payments reduce, and nothing else. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +YEAR = 2026 +WEEKS = 52 +# Sch IV para 1: £10 a week of each payment. +DISREGARD = 10 * WEEKS +PENSIONS = np.arange(0, 20_001, 1_000) +# Weekly AFCS payments for invariant 3, through the disregard and beyond. +AFCS_GRID = np.arange(0, 60.01, 2.5) +PROPERTY_SETTINGS = settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +REGIONS = { + "ENGLAND": "NORTH_WEST", + "WALES": "WALES", + "SCOTLAND": "SCOTLAND", +} + + +def money(high): + return st.floats(0, high, allow_nan=False, allow_infinity=False) + + +def weekly_payment(): + """Annual amount of a weekly payment, often at or near £10 a week.""" + weekly = st.one_of( + st.just(0.0), + st.sampled_from([5.0, 9.99, 10.0, 10.01, 25.0, 80.0]), + money(150), + ) + return weekly.map(lambda w: w * WEEKS) + + +@st.composite +def adult(draw, min_age=67): + return dict( + age=draw(st.integers(min_age, 95)), + state_pension=draw(money(15_000)), + afcs=draw(weekly_payment()), + war_widows_pension=draw(weekly_payment()), + ) + + +@st.composite +def family(draw, min_age=67): + adults = [draw(adult(min_age)) for _ in range(draw(st.integers(1, 2)))] + return dict( + adults=adults, + # A carer in some families, by caring hours or a reported award. + carer=draw(st.one_of(st.none(), st.integers(0, len(adults) - 1))), + by_hours=draw(st.booleans()), + country=draw(st.sampled_from(tuple(REGIONS))), + rent=draw(money(12_000)), + council_tax=draw(money(3_000)), + savings=draw(st.one_of(st.just(0.0), money(10_000))), + private_pension=draw(money(15_000)), + # Invariant 4 reassigns the payments in this order. + order=draw(st.permutations(range(2 * len(adults)))), + ) + + +def person_inputs(fam, j, a, private_pension, afcs, war_widows_pension): + person = { + "age": {YEAR: a["age"]}, + "state_pension": {YEAR: a["state_pension"]}, + # All private pension goes to the first adult. + "private_pension_income": {YEAR: float(private_pension) * (j == 0)}, + "afcs": {YEAR: float(afcs)}, + "war_widows_pension": {YEAR: float(war_widows_pension)}, + } + if fam["carer"] == j: + if fam["by_hours"]: + person["care_hours"] = {YEAR: 35} + else: + person["carers_allowance_reported"] = {YEAR: 1} + return person + + +def household(fam, names): + return { + "members": names, + "country": {YEAR: fam["country"]}, + "region": {YEAR: REGIONS[fam["country"]]}, + "tenure_type": {YEAR: "RENT_FROM_COUNCIL"}, + "rent": {YEAR: fam["rent"]}, + "council_tax": {YEAR: fam["council_tax"]}, + "savings": {YEAR: fam["savings"]}, + } + + +def simulate(rows): + """One Simulation with one benefit unit per row. + + Each row is (family, private pension, [(afcs, war widow's pension) per + adult]). Returns a function giving a benefit-unit array per variable. + """ + people, benunits, households = {}, {}, {} + for r, (fam, private_pension, payments) in enumerate(rows): + names = [] + for j, (a, (afcs, wwp)) in enumerate(zip(fam["adults"], payments)): + name = f"p{r}_{j}" + people[name] = person_inputs(fam, j, a, private_pension, afcs, wwp) + names.append(name) + benunits[f"b{r}"] = {"members": names} + households[f"h{r}"] = household(fam, names) + simulation = Simulation( + situation={ + "people": people, + "benunits": benunits, + "households": households, + } + ) + + def get(variable): + return np.asarray(simulation.calculate(variable, YEAR, map_to="benunit")) + + return get + + +def drawn_payments(fam): + return [(a["afcs"], a["war_widows_pension"]) for a in fam["adults"]] + + +def reassigned_payments(fam): + """The family's payments, reassigned among sources and adults by `order`.""" + amounts = [amount for pair in drawn_payments(fam) for amount in pair] + moved = [amounts[k] for k in fam["order"]] + return [(moved[2 * j], moved[2 * j + 1]) for j in range(len(fam["adults"]))] + + +def pension_grid(families, payments=drawn_payments): + """Each family once per private pension on the PENSIONS grid.""" + rows = [(fam, pension, payments(fam)) for fam in families for pension in PENSIONS] + get = simulate(rows) + shape = (len(families), len(PENSIONS)) + return lambda variable: get(variable).reshape(shape) + + +@PROPERTY_SETTINGS +@given(st.lists(family(), min_size=1, max_size=4)) +def test_pension_credit_and_housing_benefit_differ_only_by_war_pensions( + families, +): + get = pension_grid(families) + gc = get("guarantee_credit") + sc = get("savings_credit") + hb_income = get("housing_benefit_applicable_income") + hb_eligible = get("housing_benefit_eligible").astype(bool) + pc_income = get("pension_credit_income") + war = get("pension_credit_war_pension_income") + before_disregard = hb_income + get("housing_benefit_applicable_income_disregard") + for i, fam in enumerate(families): + # Where neither credit is due: with a savings-credit-only award, + # Housing Benefit takes the Secretary of State's assessment plus the + # savings credit (HB (SPC) Regs 2006 reg 27), by design. + compared = (gc[i] <= 0) & (sc[i] <= 0) & hb_eligible[i] & (hb_income[i] > 0) + compared &= pc_income[i] > 0 + assert np.allclose( + before_disregard[i][compared], + (pc_income[i] - war[i])[compared], + atol=0.01, + ), (fam, before_disregard[i][compared], pc_income[i][compared]) + + +@PROPERTY_SETTINGS +@given(st.lists(family(), min_size=1, max_size=4)) +def test_counted_war_pension_income_bounds_and_closed_form(families): + get = pension_grid(families) + war = get("pension_credit_war_pension_income") + gross = get("afcs") + get("war_widows_pension") + for i, fam in enumerate(families): + expected = sum( + max(0.0, payment - DISREGARD) + for pair in drawn_payments(fam) + for payment in pair + ) + assert np.all(war[i] >= 0), fam + assert np.all(war[i] <= gross[i] + 0.01), fam + assert np.allclose(war[i], expected, atol=0.01), (fam, war[i], expected) + + +@PROPERTY_SETTINGS +@given(st.lists(family(), min_size=1, max_size=4)) +def test_guarantee_credit_does_not_rise_with_an_afcs_payment(families): + # The first adult's AFCS payment runs over the grid; their war widow's + # pension and the partner's payments stay as drawn. + # No private pension, so that Guarantee Credit is usually paid and the + # property is not met vacuously by a Guarantee Credit of nil throughout. + rows = [ + ( + fam, + 0.0, + [(weekly * WEEKS, fam["adults"][0]["war_widows_pension"])] + + drawn_payments(fam)[1:], + ) + for fam in families + for weekly in AFCS_GRID + ] + gc = simulate(rows)("guarantee_credit").reshape(len(families), len(AFCS_GRID)) + within_disregard = AFCS_GRID <= 10 + for i, fam in enumerate(families): + assert np.all(np.diff(gc[i]) <= 0.01), (fam, gc[i]) + assert np.allclose(gc[i][within_disregard], gc[i][0], atol=0.01), ( + fam, + gc[i], + ) + + +@PROPERTY_SETTINGS +@given(st.lists(family(), min_size=1, max_size=4)) +def test_reassigning_payments_between_sources_and_partners(families): + get = pension_grid(families) + get_moved = pension_grid(families, payments=reassigned_payments) + for variable in ["pension_credit_income", "guarantee_credit"]: + original, after = get(variable), get_moved(variable) + for i, fam in enumerate(families): + assert np.allclose(original[i], after[i], atol=0.01), (fam, variable) + + +def no_payments(fam): + return [(0.0, 0.0) for _ in fam["adults"]] + + +# Aged 77 or over, so that Savings Credit, and with it the savings-credit-only +# route, is often due. +@PROPERTY_SETTINGS +@given(st.lists(family(min_age=77), min_size=1, max_size=4)) +def test_housing_benefit_and_ctr_disregard_war_pensions_on_every_route(families): + with_payments = pension_grid(families) + without = pension_grid(families, payments=no_payments) + variables = [ + "guarantee_credit", + "in_receipt_of_savings_credit_only", + "pension_credit", + "housing_benefit_applicable_income", + "council_tax_reduction_applicable_income", + ] + a = {v: with_payments(v) for v in variables} + b = {v: without(v) for v in variables} + same_route = ( + (a["guarantee_credit"] <= 0) + & (b["guarantee_credit"] <= 0) + & ( + a["in_receipt_of_savings_credit_only"].astype(bool) + == b["in_receipt_of_savings_credit_only"].astype(bool) + ) + ) + paid_change = a["pension_credit"] - b["pension_credit"] + for income in [ + "housing_benefit_applicable_income", + "council_tax_reduction_applicable_income", + ]: + compared = same_route & (a[income] > 0) & (b[income] > 0) + assert np.allclose( + (a[income] - b[income])[compared], paid_change[compared], atol=0.01 + ), (families, income) diff --git a/policyengine_uk/tests/test_war_widows_pension.py b/policyengine_uk/tests/test_war_widows_pension.py index 2356787ed0..8d12f38e13 100644 --- a/policyengine_uk/tests/test_war_widows_pension.py +++ b/policyengine_uk/tests/test_war_widows_pension.py @@ -52,10 +52,21 @@ def test_war_widows_pension_reported_declares_cpi_uprating(): @pytest.mark.parametrize("aggregate", AGGREGATES_THAT_COUNT_BSP) def test_war_widows_pension_counts_wherever_bsp_does(aggregate): + # £40,000 of private pension keeps the widow out of every means test, so + # each aggregate moves by the payment itself. War widow's pension is + # Pension Credit income (SPCA 2002 s.15(1)(g)) and BSP is not (SPC Regs + # 2002 reg 15(1)(n)), so for a widow on Pension Credit the two would + # change the totals by different amounts. def value(person_inputs): sim = Simulation( situation={ - "people": {"widow": {"age": 70, **person_inputs}}, + "people": { + "widow": { + "age": 70, + "private_pension_income": {2026: 40_000}, + **person_inputs, + } + }, "benunits": {"benunit": {"members": ["widow"]}}, "households": {"household": {"members": ["widow"]}}, } diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_savings_credit_only_income.py b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_savings_credit_only_income.py index 74fb57f8e4..684aa7a543 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_savings_credit_only_income.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/applicable_income/housing_benefit_savings_credit_only_income.py @@ -33,11 +33,13 @@ class housing_benefit_savings_credit_only_income(Variable): "maintenance disregards (neither income definition counts " "maintenance), the exempt work disregard, the income and capital of a " "partner treated as a household member but not in the Pension Credit " - "assessment, income of a non-dependant treated as the claimant's, and " - "war pensions (Great Britain: local schemes under section 134(8) of " + "assessment, and income of a non-dependant treated as the claimant's. " + "War pensions (Great Britain: local schemes under section 134(8) of " "the Social Security Administration Act 1992; Northern Ireland: war " - "pension income above £10 disregarded under Schedule 6 paragraph 1); " - "the model has no war pension income." + "pension income above £10 disregarded under Schedule 6 paragraph 1) " + "are taken as wholly disregarded, as in the general route: the war " + "pension income the Secretary of State counts " + "(pension_credit_war_pension_income) is removed from the assessment." ) definition_period = YEAR unit = GBP @@ -51,8 +53,16 @@ class housing_benefit_savings_credit_only_income(Variable): def formula(benunit, period, parameters): # SI 2006/214 reg 27(1) and (3) (NI: SR 2006/406 reg 25): the - # Secretary of State's assessment of net income for the award. - pension_credit_income = benunit("pension_credit_income", period) + # Secretary of State's assessment of net income for the award, less + # the war pension income it counts: the model takes the authority to + # disregard war pensions in full under a section 134(8) local scheme, + # as the general route does (HB/CTB Guidance Manual P2.576 lets it + # modify the Pension Service's figure for that). + pension_credit_income = max_( + 0, + benunit("pension_credit_income", period) + - benunit("pension_credit_war_pension_income", period), + ) # Reg 27(4)(a): the savings credit payable. A savings-credit-only # award pays only the savings credit, so this is the Pension Credit # paid (under the Pension Credit freeze, the frozen amount). diff --git a/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_war_pension_income.py b/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_war_pension_income.py new file mode 100644 index 0000000000..1abb1d31ec --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_war_pension_income.py @@ -0,0 +1,46 @@ +from policyengine_uk.model_api import * + + +class pension_credit_war_pension_income(Variable): + label = "War pension income counted for Pension Credit" + entity = BenUnit + definition_period = YEAR + value_type = float + unit = GBP + documentation = ( + "The claimant's and partner's war disablement pensions, war widow's or " + "widower's pensions and Armed Forces Compensation Scheme guaranteed " + "income payments, each payment less the Sch. IV para. 1 disregard " + "(£10 of each payment, the reading PolicyEngine applies). afcs holds " + "Armed Forces Compensation Scheme payments, including guaranteed " + "income payments, and war disablement pensions together, so a person " + "with both gets one £10 on the sum, not £10 on each. war_widows_pension " + "holds war widow's or widower's pensions. Each of the two variables " + "gets its own weekly disregard, which does not pass to the other or to " + "the other member of a couple. Dependants' and other benefit-unit " + "members' payments do not count (State Pension Credit Act 2002 s.5; " + "reg. 14). Not modelled: Sch. IV para. 1(cc), under which the unused " + "part of the £10 on a guaranteed income payment abated below £10 by a " + "pension or payment within article 31(1)(a) or (b) of the Armed Forces " + "and Reserve Forces (Compensation Scheme) Order 2005 (an Armed Forces " + "Pension Scheme pension or Early Departure Scheme payment) comes off " + "that pension or payment; the " + "components that Sch. IV paras 2 to 6 and 12 disregard in full; and " + "reg. 15(5)(a) and (ab) payments that count in full. The data cannot " + "separate any of them." + ) + reference = ( + "https://www.legislation.gov.uk/ukpga/2002/16/section/15", + "https://www.legislation.gov.uk/uksi/2002/1792/regulation/14", + "https://www.legislation.gov.uk/uksi/2002/1792/regulation/15", + "https://www.legislation.gov.uk/uksi/2002/1792/schedule/IV/paragraph/1", + ) + + def formula(benunit, period, parameters): + p = parameters(period).gov.dwp.pension_credit.income + person = benunit.members + disregard = p.war_pension_disregard * WEEKS_IN_YEAR + counted = 0 + for payment in ["afcs", "war_widows_pension"]: + counted += max_(person(payment, period) - disregard, 0) + return benunit.sum(counted * person("is_claimant_or_partner", period)) diff --git a/policyengine_uk/variables/gov/dwp/war_widows_pension.py b/policyengine_uk/variables/gov/dwp/war_widows_pension.py index 762833d871..e6987680b1 100644 --- a/policyengine_uk/variables/gov/dwp/war_widows_pension.py +++ b/policyengine_uk/variables/gov/dwp/war_widows_pension.py @@ -5,7 +5,25 @@ class war_widows_pension(Variable): value_type = float entity = Person label = "War Widow's or Widower's Pension" + documentation = ( + "A war widow's or widower's pension as defined in State Pension Credit " + "Act 2002 s.17(1): a widow's, widower's or surviving civil partner's " + "pension or allowance granted in respect of a death due to service or " + "war injury and payable under an enactment named in the definition of " + "war disablement pension, or granted under a scheme in s.641(1)(e) or " + "(f) of the Income Tax (Earnings and Pensions) Act 2003. This is the " + "gross payment. Amounts wholly disregarded by " + "State Pension Credit Regulations 2002 Sch. IV paras 2 to 6 and 12 " + "(for example a supplementary pension under article 23(2) of the " + "Service Pensions Order 2006) are not separated. Family Resources " + "Survey benefit code 9 belongs here; the datasets still add it to " + "bsp_reported (policyengine-uk-data#578, microcosm#1222)." + ) definition_period = YEAR unit = GBP + reference = ( + "https://www.legislation.gov.uk/ukpga/2002/16/section/17", + "https://www.legislation.gov.uk/ukpga/2003/1/section/639", + ) adds = ["war_widows_pension_reported"] 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..d44afca768 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 @@ -162,9 +162,14 @@ def formula(benunit, period, parameters): has_savings_credit_only_award = benunit( "in_receipt_of_savings_credit_only", period ) - savings_credit_only_income = benunit("pension_credit_income", period) + benunit( - "pension_credit", period - ) + # The general route counts no war pension (as Housing Benefit, a full + # disregard), so the war pension income in the Secretary of State's + # assessment is taken out here too. + savings_credit_only_income = max_( + 0, + benunit("pension_credit_income", period) + - benunit("pension_credit_war_pension_income", period), + ) + benunit("pension_credit", period) return select( [in_receipt_of_guarantee_credit, has_savings_credit_only_award], [0, savings_credit_only_income],