From 4a5de76f30ddb83d469f9d3e4697f453539c5104 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 16:12:13 -0400 Subject: [PATCH 1/8] Count war pensions and AFCS payments in Pension Credit income MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit State Pension Credit Act 2002 s.15(1)(g) counts war disablement pensions and war widow's or widower's pensions, and SPC Regs reg 15(5)(aa) counts AFCS guaranteed income payments. Sch IV para 1 disregards £10 a week of them. Pension Credit income counted none of them. Add pension_credit_war_pension_income: the claimant's and partner's afcs and war_widows_pension, each person's less the weekly disregard (a new parameter, gov.dwp.pension_credit.income.war_pension_disregard), and put it in the Pension Credit income list. Add war_widows_pension and war_widows_pension_reported, counted next to bsp in the gross benefit, HBAI and spending totals, so moving FRS code 9 out of bsp_reported keeps those totals unchanged. Housing Benefit is not changed. Add YAML cases from 2026-27 statutory rates (SI 2026/148) and Hypothesis properties: the Pension Credit and Housing Benefit income differential, bounds and closed form, Guarantee Credit monotonicity and moving amounts between sources within a person. Co-Authored-By: Claude Opus 5.5 --- .../pension-credit-war-pensions.fixed.md | 1 + .../guarantee_credit/income.yaml | 3 + .../income/war_pension_disregard.yaml | 14 + .../pension_credit_war_pension_income.yaml | 332 ++++++++++++++++++ ...t_pension_credit_war_pension_properties.py | 263 ++++++++++++++ .../pension_credit_war_pension_income.py | 34 ++ .../variables/gov/dwp/war_widows_pension.py | 15 + 7 files changed, 662 insertions(+) create mode 100644 changelog.d/pension-credit-war-pensions.fixed.md create mode 100644 policyengine_uk/parameters/gov/dwp/pension_credit/income/war_pension_disregard.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_war_pension_income.yaml create mode 100644 policyengine_uk/tests/test_pension_credit_war_pension_properties.py create mode 100644 policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_war_pension_income.py 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..a88f758169 --- /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 payments of the claimant and partner in Pension Credit income, less £10 a week for each person (State Pension Credit Regulations 2002 Sch. IV para. 1). Add a `war_widows_pension` input, counted in the benefit totals alongside `bsp`. 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..f641f998d6 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,9 @@ 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)), each person's 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..65a079f26f --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/pension_credit/income/war_pension_disregard.yaml @@ -0,0 +1,14 @@ +description: Weekly amount of a person's 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..3656a8c96c --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/pension_credit_war_pension_income.yaml @@ -0,0 +1,332 @@ +# 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 applies one £10 a +# week to each person's qualifying payments together (para 1's "£10 of any of +# the following" and para 11(3)(b)'s single "£10 ... disregarded in accordance +# with paragraph 1(a) to (f)"), and computes the claimant's and the partner's +# amounts separately (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. Unchanged behaviour: passes on the base too. + 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. Unchanged behaviour: passes on the base too. + 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 one £10. + 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 + 15 - 10) * 52 = 25 * 52 = 1,300; 12,376 - 1,300 = 11,076. + pension_credit_war_pension_income: (20 + 15 - 10) * 52 + pension_credit_income: (20 + 15 - 10) * 52 + guarantee_credit: (238 - 25) * 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: + # passes on the base too. + 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 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..91ca7a23ed --- /dev/null +++ b/policyengine_uk/tests/test_pension_credit_war_pension_properties.py @@ -0,0 +1,263 @@ +"""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 person's 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 Guarantee Credit is not paid 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)(e) and (g) count these payments less £10 (Sch 5 para 1), 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`. 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 + and partner of max(0, AFCS + war widow's pension - £520 a year). +3. Monotone. Guarantee Credit does not rise as one person's AFCS payment + rises, and does not change while the payment is at or below £10 a week. +4. Metamorphic. Moving an amount between `afcs` and `war_widows_pension` + within one person changes neither Pension Credit income nor Guarantee + Credit: one £10 a week applies to each person's payments together. +""" + +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. +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): + return dict( + age=draw(st.integers(67, 95)), + state_pension=draw(money(15_000)), + afcs=draw(weekly_payment()), + war_widows_pension=draw(weekly_payment()), + # Share of the person's payments moved to the other source in + # invariant 4. + share=draw(st.floats(0, 1)), + ) + + +@st.composite +def family(draw): + adults = [draw(adult()) 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)), + ) + + +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 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") + 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") + carer_benefit = get("carers_allowance") + get("carer_support_payment") + for i, fam in enumerate(families): + if fam["carer"] is not None: + assert np.all(carer_benefit[i] > 0), fam + compared = (gc[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, a["afcs"] + a["war_widows_pension"] - DISREGARD) + for a in fam["adults"] + ) + 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 war widow's pension is set to zero so that the grid + # is the whole of their payments; the partner keeps their drawn payments. + rows = [ + ( + fam, + fam["private_pension"], + [(weekly * WEEKS, 0.0)] + 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], + ) + + +def moved(fam): + """Each person's total payments, split between the sources by `share`.""" + return [ + ( + a["share"] * (a["afcs"] + a["war_widows_pension"]), + (1 - a["share"]) * (a["afcs"] + a["war_widows_pension"]), + ) + for a in fam["adults"] + ] + + +@PROPERTY_SETTINGS +@given(st.lists(family(), min_size=1, max_size=4)) +def test_moving_payments_between_sources_within_a_person(families): + get = pension_grid(families) + get_moved = pension_grid(families, payments=moved) + 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) 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..92e6bb890d --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/pension_credit/pension_credit_war_pension_income.py @@ -0,0 +1,34 @@ +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 payments " + "(afcs, which holds guaranteed income payments and war disablement " + "pensions), less the Sch. IV para. 1 disregard. One weekly disregard " + "applies to each person's payments together, and the claimant and " + "partner each have their own (reg. 14). Dependants' and other " + "benefit-unit members' payments do not count. 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, are not separated in the data." + ) + 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 + payments = add(person, period, ["afcs", "war_widows_pension"]) + disregard = p.war_pension_disregard * WEEKS_IN_YEAR + counted = max_(payments - 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..8bd972472a 100644 --- a/policyengine_uk/variables/gov/dwp/war_widows_pension.py +++ b/policyengine_uk/variables/gov/dwp/war_widows_pension.py @@ -5,7 +5,22 @@ 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. 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"] From 5a006ab5bf95347ef26d7680c53108cee7a087fb Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 15:37:25 -0400 Subject: [PATCH 2/8] Finish builder edits after review (checkpoint) --- .../pension-credit-war-pensions.fixed.md | 2 +- .../guarantee_credit/income.yaml | 3 +- .../income/war_pension_disregard.yaml | 2 +- .../pension_credit_war_pension_income.yaml | 49 +++++++++++---- ...t_pension_credit_war_pension_properties.py | 59 +++++++++---------- .../pension_credit_war_pension_income.py | 23 ++++---- 6 files changed, 83 insertions(+), 55 deletions(-) diff --git a/changelog.d/pension-credit-war-pensions.fixed.md b/changelog.d/pension-credit-war-pensions.fixed.md index a88f758169..32de2fb3bd 100644 --- a/changelog.d/pension-credit-war-pensions.fixed.md +++ b/changelog.d/pension-credit-war-pensions.fixed.md @@ -1 +1 @@ -- Count war disablement pensions, war widow's or widower's pensions and Armed Forces Compensation Scheme payments of the claimant and partner in Pension Credit income, less £10 a week for each person (State Pension Credit Regulations 2002 Sch. IV para. 1). Add a `war_widows_pension` input, counted in the benefit totals alongside `bsp`. +- Count war disablement pensions, war widow's or widower's pensions and Armed Forces Compensation Scheme 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). Add a `war_widows_pension` input, counted in the benefit totals alongside `bsp`. 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 f641f998d6..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 @@ -15,7 +15,8 @@ values: - pension_credit_earnings - working_tax_credit # War pensions (SPCA s.15(1)(g)) and AFCS guaranteed income payments - # (reg. 15(5)(aa)), each person's less £10 a week (Sch. IV para. 1). + # (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 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 index 65a079f26f..1f0a5066ac 100644 --- 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 @@ -1,4 +1,4 @@ -description: Weekly amount of a person's war disablement pension, war widow's or widower's pension and Armed Forces Compensation Scheme guaranteed income payment disregarded from income for Pension Credit. +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: 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 index 3656a8c96c..fef73ee0e7 100644 --- 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 @@ -5,13 +5,13 @@ # 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 applies one £10 a -# week to each person's qualifying payments together (para 1's "£10 of any of -# the following" and para 11(3)(b)'s single "£10 ... disregarded in accordance -# with paragraph 1(a) to (f)"), and computes the claimant's and the partner's -# amounts separately (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. +# 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 @@ -112,7 +112,7 @@ 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 one £10. +- 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: @@ -130,10 +130,10 @@ members: [person1] country: ENGLAND output: - # (20 + 15 - 10) * 52 = 25 * 52 = 1,300; 12,376 - 1,300 = 11,076. - pension_credit_war_pension_income: (20 + 15 - 10) * 52 - pension_credit_income: (20 + 15 - 10) * 52 - guarantee_credit: (238 - 25) * 52 + # ((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 @@ -249,6 +249,7 @@ output: is_claimant_or_partner: [true, true, false, false] # Only the claimant's and partner's payments count (SPCA s.5; reg 14). + # Unchanged behaviour: passes on the base too. pension_credit_war_pension_income: 0 pension_credit_income: 0 @@ -330,3 +331,27 @@ # 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 diff --git a/policyengine_uk/tests/test_pension_credit_war_pension_properties.py b/policyengine_uk/tests/test_pension_credit_war_pension_properties.py index 91ca7a23ed..54b8f11291 100644 --- a/policyengine_uk/tests/test_pension_credit_war_pension_properties.py +++ b/policyengine_uk/tests/test_pension_credit_war_pension_properties.py @@ -5,7 +5,7 @@ 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 person's less +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 @@ -18,7 +18,7 @@ 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)(e) and (g) count these payments less £10 (Sch 5 para 1), but the + 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` @@ -28,13 +28,15 @@ 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 - and partner of max(0, AFCS + war widow's pension - £520 a year). + 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 the payment is at or below £10 a week. -4. Metamorphic. Moving an amount between `afcs` and `war_widows_pension` - within one person changes neither Pension Credit income nor Guarantee - Credit: one £10 a week applies to each person's payments together. + 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. """ import numpy as np @@ -45,7 +47,7 @@ YEAR = 2026 WEEKS = 52 -# Sch IV para 1: £10 a week. +# 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. @@ -84,9 +86,6 @@ def adult(draw): state_pension=draw(money(15_000)), afcs=draw(weekly_payment()), war_widows_pension=draw(weekly_payment()), - # Share of the person's payments moved to the other source in - # invariant 4. - share=draw(st.floats(0, 1)), ) @@ -103,6 +102,8 @@ def family(draw): 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)))), ) @@ -168,6 +169,13 @@ 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] @@ -209,8 +217,9 @@ def test_counted_war_pension_income_bounds_and_closed_form(families): gross = get("afcs") + get("war_widows_pension") for i, fam in enumerate(families): expected = sum( - max(0.0, a["afcs"] + a["war_widows_pension"] - DISREGARD) - for a in fam["adults"] + 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 @@ -220,13 +229,14 @@ def test_counted_war_pension_income_bounds_and_closed_form(families): @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 war widow's pension is set to zero so that the grid - # is the whole of their payments; the partner keeps their drawn payments. + # The first adult's AFCS payment runs over the grid; their war widow's + # pension and the partner's payments stay as drawn. rows = [ ( fam, fam["private_pension"], - [(weekly * WEEKS, 0.0)] + drawn_payments(fam)[1:], + [(weekly * WEEKS, fam["adults"][0]["war_widows_pension"])] + + drawn_payments(fam)[1:], ) for fam in families for weekly in AFCS_GRID @@ -241,22 +251,11 @@ def test_guarantee_credit_does_not_rise_with_an_afcs_payment(families): ) -def moved(fam): - """Each person's total payments, split between the sources by `share`.""" - return [ - ( - a["share"] * (a["afcs"] + a["war_widows_pension"]), - (1 - a["share"]) * (a["afcs"] + a["war_widows_pension"]), - ) - for a in fam["adults"] - ] - - @PROPERTY_SETTINGS @given(st.lists(family(), min_size=1, max_size=4)) -def test_moving_payments_between_sources_within_a_person(families): +def test_reassigning_payments_between_sources_and_partners(families): get = pension_grid(families) - get_moved = pension_grid(families, payments=moved) + 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): 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 index 92e6bb890d..7a3cc2d79c 100644 --- 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 @@ -9,14 +9,16 @@ class pension_credit_war_pension_income(Variable): unit = GBP documentation = ( "The claimant's and partner's war disablement pensions, war widow's or " - "widower's pensions and Armed Forces Compensation Scheme payments " - "(afcs, which holds guaranteed income payments and war disablement " - "pensions), less the Sch. IV para. 1 disregard. One weekly disregard " - "applies to each person's payments together, and the claimant and " - "partner each have their own (reg. 14). Dependants' and other " - "benefit-unit members' payments do not count. 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, are not separated in the data." + "widower's pensions and Armed Forces Compensation Scheme payments, each " + "payment less the Sch. IV para. 1 disregard. afcs holds Armed Forces " + "Compensation Scheme payments, including guaranteed income payments, " + "and war disablement pensions; war_widows_pension holds war widow's or " + "widower's pensions. Each of the two gets its own weekly disregard, " + "which does not pass to the other payment 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). 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, are not separated in the data." ) reference = ( "https://www.legislation.gov.uk/ukpga/2002/16/section/15", @@ -28,7 +30,8 @@ class pension_credit_war_pension_income(Variable): def formula(benunit, period, parameters): p = parameters(period).gov.dwp.pension_credit.income person = benunit.members - payments = add(person, period, ["afcs", "war_widows_pension"]) disregard = p.war_pension_disregard * WEEKS_IN_YEAR - counted = max_(payments - disregard, 0) + 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)) From 22ddb9e61200b214eb7a65f2e20bf8560c7a1b71 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 16:27:50 -0400 Subject: [PATCH 3/8] Drop the war_widows_pension claim from the changelog; #2382 added it Co-Authored-By: Claude Opus 5.5 --- changelog.d/pension-credit-war-pensions.fixed.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.d/pension-credit-war-pensions.fixed.md b/changelog.d/pension-credit-war-pensions.fixed.md index 32de2fb3bd..abcdc35a3d 100644 --- a/changelog.d/pension-credit-war-pensions.fixed.md +++ b/changelog.d/pension-credit-war-pensions.fixed.md @@ -1 +1 @@ -- Count war disablement pensions, war widow's or widower's pensions and Armed Forces Compensation Scheme 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). Add a `war_widows_pension` input, counted in the benefit totals alongside `bsp`. +- Count war disablement pensions, war widow's or widower's pensions and Armed Forces Compensation Scheme 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). From f8b8459d91f0e286cad7db97dd175dadb559936b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 17:57:31 -0400 Subject: [PATCH 4/8] Compare war pension income with HB only 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), so the differential now excludes those points. Drop the assertion that every drawn carer is paid a carer's benefit: since #1994, State Pension and other overlapping benefits can reduce Carer's Allowance to nil. On the derandomized draws the differential compares 278 of 588 points, 214 with counted war pension income above the disregard. All four properties pass in 75 s. Co-Authored-By: Claude Opus 5.5 --- .../test_pension_credit_war_pension_properties.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/policyengine_uk/tests/test_pension_credit_war_pension_properties.py b/policyengine_uk/tests/test_pension_credit_war_pension_properties.py index 54b8f11291..bb9869283c 100644 --- a/policyengine_uk/tests/test_pension_credit_war_pension_properties.py +++ b/policyengine_uk/tests/test_pension_credit_war_pension_properties.py @@ -13,8 +13,10 @@ 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 Guarantee Credit is not paid and - neither income floor binds, Housing Benefit applicable income plus its +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 @@ -191,16 +193,17 @@ def test_pension_credit_and_housing_benefit_differ_only_by_war_pensions( ): 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") - carer_benefit = get("carers_allowance") + get("carer_support_payment") for i, fam in enumerate(families): - if fam["carer"] is not None: - assert np.all(carer_benefit[i] > 0), fam - compared = (gc[i] <= 0) & hb_eligible[i] & (hb_income[i] > 0) + # 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], From 955329b86cb12d2875d62ac643dc3fe1a2d0115a Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 17:59:36 -0400 Subject: [PATCH 5/8] Correct a YAML comment: Case 11 asserts the new variable, so it fails on main Co-Authored-By: Claude Opus 5.5 --- .../dwp/pension_credit/pension_credit_war_pension_income.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index fef73ee0e7..4c54603f77 100644 --- 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 @@ -299,7 +299,7 @@ country: ENGLAND output: # Reg 15(1)(n) excepts bereavement support payment. Unchanged behaviour: - # passes on the base too. + # 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 From c1a6f3a0c96b127b9d9a728a4d4fac6cea8d403c Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 21:57:17 -0400 Subject: [PATCH 6/8] Keep the war widow's pension aggregate test outside the means tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test_war_widows_pension_counts_wherever_bsp_does (#2382) checked that adding £5,000 of war widow's pension raises household_benefits, HBAI net income and government spending by £5,000, as BSP does. Its widow was on Pension Credit, and war widow's pension is now Pension Credit income (SPCA 2002 s.15(1)(g)) while BSP is not (reg 15(1)(n)), so her Pension Credit fell and the totals rose by £520. Give her £40,000 of private pension, which keeps her out of every means test, so the test again measures how the aggregates count the payment. Co-Authored-By: Claude Opus 5.5 --- policyengine_uk/tests/test_war_widows_pension.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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"]}}, } From 4c1cc3759d336c3d5c5d93ecdc50d99be41bc5ee Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 22:18:45 -0400 Subject: [PATCH 7/8] Keep war pensions out of savings-credit-only HB and CTR; review fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Independent review of #2508 found that the savings-credit-only routes of Housing Benefit (HB (SPC) Regs 2006 reg 27) and Council Tax Reduction take pension_credit_income, which now counts war pensions less £10, while their general routes count none. Under the model's assumption that authorities disregard war pensions in full under SSAA 1992 s.134(8) (HB/CTB Guidance Manual P2.576: an authority "may modify The Pension Service's calculation" for a greater war pension disregard), both routes now remove pension_credit_war_pension_income. Also: - YAML Case 14: a savings-credit-only claimant with an AFCS payment; HB and CTR income 13,954 - 1,040 + 302.616 = 13,216.616. - Property 5: with and without the payments, HB and CTR income change only by the change in Pension Credit paid, on both routes. Property 3 now starts from nil private pension so Guarantee Credit is usually paid. - Document that afcs sums AFCS payments and war disablement pensions (one £10 on the sum) and that Sch IV para 1(cc)'s abated-GIP rule is not modelled; quote the s.17(1) definition of war widow's pension fully. - Drop three YAML comments that said a case passes on main; name guaranteed income payments in the changelog. Co-Authored-By: Claude Opus 5.5 --- .../pension-credit-war-pensions.fixed.md | 2 +- .../pension_credit_war_pension_income.yaml | 48 +++++++++++++- ...t_pension_credit_war_pension_properties.py | 63 +++++++++++++++++-- ...sing_benefit_savings_credit_only_income.py | 20 ++++-- .../pension_credit_war_pension_income.py | 26 +++++--- .../variables/gov/dwp/war_widows_pension.py | 5 +- ...council_tax_reduction_applicable_income.py | 9 ++- 7 files changed, 144 insertions(+), 29 deletions(-) diff --git a/changelog.d/pension-credit-war-pensions.fixed.md b/changelog.d/pension-credit-war-pensions.fixed.md index abcdc35a3d..865116033e 100644 --- a/changelog.d/pension-credit-war-pensions.fixed.md +++ b/changelog.d/pension-credit-war-pensions.fixed.md @@ -1 +1 @@ -- Count war disablement pensions, war widow's or widower's pensions and Armed Forces Compensation Scheme 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). +- 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/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 index 4c54603f77..b8d49ad37f 100644 --- 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 @@ -63,7 +63,7 @@ members: [person1] country: ENGLAND output: - # max(5 - 10, 0) * 52 = 0. Unchanged behaviour: passes on the base too. + # max(5 - 10, 0) * 52 = 0. pension_credit_war_pension_income: 0 pension_credit_income: 0 guarantee_credit: 238 * 52 @@ -85,7 +85,7 @@ members: [person1] country: ENGLAND output: - # max(10 - 10, 0) * 52 = 0. Unchanged behaviour: passes on the base too. + # max(10 - 10, 0) * 52 = 0. pension_credit_war_pension_income: 0 pension_credit_income: 0 guarantee_credit: 238 * 52 @@ -249,7 +249,6 @@ output: is_claimant_or_partner: [true, true, false, false] # Only the claimant's and partner's payments count (SPCA s.5; reg 14). - # Unchanged behaviour: passes on the base too. pension_credit_war_pension_income: 0 pension_credit_income: 0 @@ -355,3 +354,46 @@ 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: 250 * 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: + # Income tax on the private pension: (13,000 - 12,570) * 0.2 = 86; the AFCS + # payment is not taxable income. + income_tax: 86 + pension_credit_war_pension_income: (30 - 10) * 52 + # 13,000 + 1,040 - 86 = 13,954, above the 12,376 minimum guarantee. + pension_credit_income: 13_954 + guarantee_credit: 0 + # Amount A = min(0.6 * (238 - 208.07), 0.6 * (13,954 / 52 - 208.07)) * 52 + # = 0.6 * 29.93 * 52 = 933.816; amount B = 0.4 * (13,954 - 12,376) + # = 631.20; savings credit = 302.616, so the award is savings + # credit only. + savings_credit: 302.616 + # 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,954 - 1,040 + 302.616 + # = 13,216.616. There are no earnings, so no earnings disregard. + housing_benefit_applicable_income: 13_216.616 + # The CTR pensioner scheme's savings-credit-only route, the same way. + council_tax_reduction_applicable_income: 13_216.616 diff --git a/policyengine_uk/tests/test_pension_credit_war_pension_properties.py b/policyengine_uk/tests/test_pension_credit_war_pension_properties.py index bb9869283c..122cfcb68e 100644 --- a/policyengine_uk/tests/test_pension_credit_war_pension_properties.py +++ b/policyengine_uk/tests/test_pension_credit_war_pension_properties.py @@ -24,7 +24,9 @@ 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`. That is an assumption that + 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 @@ -39,6 +41,14 @@ 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 @@ -82,9 +92,9 @@ def weekly_payment(): @st.composite -def adult(draw): +def adult(draw, min_age=67): return dict( - age=draw(st.integers(67, 95)), + age=draw(st.integers(min_age, 95)), state_pension=draw(money(15_000)), afcs=draw(weekly_payment()), war_widows_pension=draw(weekly_payment()), @@ -92,8 +102,8 @@ def adult(draw): @st.composite -def family(draw): - adults = [draw(adult()) for _ in range(draw(st.integers(1, 2)))] +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. @@ -234,10 +244,12 @@ def test_counted_war_pension_income_bounds_and_closed_form(families): 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, - fam["private_pension"], + 0.0, [(weekly * WEEKS, fam["adults"][0]["war_widows_pension"])] + drawn_payments(fam)[1:], ) @@ -263,3 +275,42 @@ def test_reassigning_payments_between_sources_and_partners(families): 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/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..a5972f1cfe 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,14 @@ 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 = 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 index 7a3cc2d79c..f12236954b 100644 --- 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 @@ -9,16 +9,22 @@ class pension_credit_war_pension_income(Variable): unit = GBP documentation = ( "The claimant's and partner's war disablement pensions, war widow's or " - "widower's pensions and Armed Forces Compensation Scheme payments, each " - "payment less the Sch. IV para. 1 disregard. afcs holds Armed Forces " - "Compensation Scheme payments, including guaranteed income payments, " - "and war disablement pensions; war_widows_pension holds war widow's or " - "widower's pensions. Each of the two gets its own weekly disregard, " - "which does not pass to the other payment 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). 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, are not separated in the data." + "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 an " + "Armed Forces Pension Scheme pension comes off that pension; 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", diff --git a/policyengine_uk/variables/gov/dwp/war_widows_pension.py b/policyengine_uk/variables/gov/dwp/war_widows_pension.py index 8bd972472a..e6987680b1 100644 --- a/policyengine_uk/variables/gov/dwp/war_widows_pension.py +++ b/policyengine_uk/variables/gov/dwp/war_widows_pension.py @@ -9,7 +9,10 @@ class war_widows_pension(Variable): "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. This is the gross payment. Amounts wholly disregarded by " + "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 " 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 55575e1987..fc3536940c 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 @@ -141,8 +141,13 @@ 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 = ( + 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], From a1eef99239109c8f9b89abaa1b650ea7644594ac Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Mon, 19 Oct 2026 22:34:19 -0400 Subject: [PATCH 8/8] Floor the savings-credit-only war pension removal; avoid reg 7(4) in Case 14 From the delta review of #2508: - Pension Credit income is net of tax on income it does not count (for example dividends), so it can be smaller than the war pension income it includes. Floor the difference at nil before adding the savings credit payable, in both the Housing Benefit and the Council Tax Reduction savings-credit-only routes. - Case 14 now uses inputs whose amount B is whole pennies (private pension 240 a week, no tax), so the model's unrounded savings credit (476.216) is the regulations' figure; the model does not apply reg 7(4) rounding. - Para 1(cc) covers any pension or payment within AFCS Order 2005 art 31(1)(a) or (b), not only an Armed Forces Pension Scheme pension. Co-Authored-By: Claude Opus 5.5 --- .../pension_credit_war_pension_income.yaml | 34 ++++++++++--------- ...sing_benefit_savings_credit_only_income.py | 6 ++-- .../pension_credit_war_pension_income.py | 7 ++-- ...council_tax_reduction_applicable_income.py | 8 ++--- 4 files changed, 31 insertions(+), 24 deletions(-) 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 index b8d49ad37f..c81788d84c 100644 --- 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 @@ -363,7 +363,7 @@ person1: age: 80 state_pension: 0 - private_pension_income: 250 * 52 + private_pension_income: 240 * 52 afcs: 30 * 52 benunits: benunit: @@ -377,23 +377,25 @@ rent: 6_000 council_tax: 1_500 output: - # Income tax on the private pension: (13,000 - 12,570) * 0.2 = 86; the AFCS - # payment is not taxable income. - income_tax: 86 + # 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 - # 13,000 + 1,040 - 86 = 13,954, above the 12,376 minimum guarantee. - pension_credit_income: 13_954 + # 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 * (13,954 / 52 - 208.07)) * 52 - # = 0.6 * 29.93 * 52 = 933.816; amount B = 0.4 * (13,954 - 12,376) - # = 631.20; savings credit = 302.616, so the award is savings - # credit only. - savings_credit: 302.616 + # 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,954 - 1,040 + 302.616 - # = 13,216.616. There are no earnings, so no earnings disregard. - housing_benefit_applicable_income: 13_216.616 - # The CTR pensioner scheme's savings-credit-only route, the same way. - council_tax_reduction_applicable_income: 13_216.616 + # 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/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 a5972f1cfe..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 @@ -58,8 +58,10 @@ def formula(benunit, period, parameters): # 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 = benunit("pension_credit_income", period) - benunit( - "pension_credit_war_pension_income", period + 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 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 index f12236954b..1abb1d31ec 100644 --- 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 @@ -20,8 +20,11 @@ class pension_credit_war_pension_income(Variable): "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 an " - "Armed Forces Pension Scheme pension comes off that pension; the " + "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." 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 fc3536940c..eb554c6373 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 @@ -144,11 +144,11 @@ def formula(benunit, period, parameters): # 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 = ( + savings_credit_only_income = max_( + 0, benunit("pension_credit_income", period) - - benunit("pension_credit_war_pension_income", period) - + benunit("pension_credit", 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],