From 4cf455a9144f66349a6fc5beec25f48bdb8e467f Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 07:37:33 -0400 Subject: [PATCH 1/5] Count the State Pension as step-one tax credit income MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit SI 2002/2006 reg 3(1) puts pension income (reg 5(1)) in step one, where only the excess over £300 counts. Reg 5(1)(a) covers "any pension to which section 577 ... of ITEPA applies", which is the State Pension (ITEPA 2003 s.577(1)), and reg 7(2) takes it out of social security income. The model counted it in step two through social_security_income, so a pensioner whose other step-one income was under £300 had tax credit income overstated by up to £300. tax_credits_current_year_income now lists step-one income and reg 7 social security income explicitly. YAML cases are computed by hand from the law; Hypothesis properties check the income against the law, against the previous formula (applied as a reform), and the award bounds that follow. Co-Authored-By: Claude Opus 5.5 --- ...ax-credits-state-pension-step-one.fixed.md | 1 + .../tax_credits_state_pension_step_one.yaml | 333 +++++++++++++++++ ...edits_state_pension_step_one_properties.py | 339 ++++++++++++++++++ .../dwp/tax_credits_current_year_income.py | 57 ++- 4 files changed, 722 insertions(+), 8 deletions(-) create mode 100644 changelog.d/tax-credits-state-pension-step-one.fixed.md create mode 100644 policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml create mode 100644 policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py diff --git a/changelog.d/tax-credits-state-pension-step-one.fixed.md b/changelog.d/tax-credits-state-pension-step-one.fixed.md new file mode 100644 index 0000000000..6f44150c05 --- /dev/null +++ b/changelog.d/tax-credits-state-pension-step-one.fixed.md @@ -0,0 +1 @@ +Count the State Pension as step-one pension income in tax credit income, so the £300 disregard on step-one income applies to it (SI 2002/2006 regs 3(1), 5(1)(a) and 7(2); ITEPA 2003 s.577). diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml new file mode 100644 index 0000000000..ea3ce3d8fd --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml @@ -0,0 +1,333 @@ +# State Pension is step-one pension income for tax credits, with the £300 +# disregard. +# +# SI 2002/2006 reg 3(1), Step One adds "the pension income (as defined in +# regulation 5(1)), the investment income ..., the property income ..., the +# foreign income ... and the notional income"; "If the result of this step is +# £300 or less, it is treated as nil. If the result of this step is more than +# £300, only the excess is taken into account in the following steps." Step Two +# adds employment income, "the social security income (as defined in +# regulation 7)", student income and miscellaneous income. +# +# Reg 5(1)(a): pension income means "any pension to which section 577 or 629 +# of ITEPA applies"; ITEPA 2003 s.577(1) applies to "the state pension". Reg +# 7(2): "Pensions under the Contributions and Benefits Act which are pension +# income by virtue of regulation 5(1)(a) are not social security income." +# Carer's Allowance (SSCBA 1992 s.70) and contributory ESA (Welfare Reform Act +# 2007 Part 1) are social security income (reg 7(1)(a)); Table 3 disregards +# neither. +# +# 2024-25 rates: +# - WTC basic element £2,435, couple element £2,500 (WTC Regs 2002 Sch 2). +# - Income threshold £7,955, or £19,995 for a claim with no WTC entitlement; +# withdrawal rate 41% (SI 2002/2008 regs 3 and 7, as uprated). +# - CTC child element £3,455; family element £545 (CTC Regs 2002 reg 7). +# - Standard minimum guarantee £218.15 a week single, £332.95 couple (SPC Regs +# 2002 reg 6). WEEKS_IN_YEAR is 52. +# +# Each pensioner reached State Pension age after 6 April 2016, so has no +# savings credit (SPCA 2002 s.3(1)(a)). The CTC family element is entered at +# £545, its statutory amount, which the model uprates. + +- name: State Pension is step one, so the £300 disregard applies to it + # Tax credit income: step one 9,500 - 300 = £9,200; step two £2,600; total + # £11,800. (With State Pension in step two it would be £12,100.) + # WTC: 2,435 - 0.41 x (11,800 - 7,955) = £858.55. + # Pension Credit income is at least 9,500 + 2,600 - 5 x 52 + 858.55 = + # £12,698.55, above the 218.15 x 52 = £11,343.80 guarantee, so no Pension + # Credit, with or without the passport. No income tax: £12,100 is within the + # personal allowance. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 9_500 + working_tax_credit_reported: 1 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + output: + tax_credits_current_year_income: 11_800 + tax_credits_applicable_income: 11_800 + working_tax_credit: 858.55 + pension_credit: 0 + +- name: Other step-one income uses part of the £300, and State Pension the rest + # Step one: 9,500 + 100 - 300 = £9,300; total 9,300 + 2,600 = £11,900. + # (With State Pension in step two: 0 + 2,600 + 9,500 = £12,100.) + # WTC: 2,435 - 0.41 x (11,900 - 7,955) = £817.55. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 9_500 + savings_interest_income: 100 + working_tax_credit_reported: 1 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + output: + tax_credits_current_year_income: 11_900 + working_tax_credit: 817.55 + pension_credit: 0 + +- name: Private pension of £300 or more already uses the disregard + # Step one: 8,500 + 1,000 - 300 = £9,200; total £11,800, as it would be with + # State Pension in step two: (1,000 - 300) + 2,600 + 8,500 = £11,800. + # WTC: 2,435 - 0.41 x (11,800 - 7,955) = £858.55. Pension Credit income + # includes the private pension: at least 8,500 + 1,000 + 2,340 + 858.55 = + # £12,698.55, so none. No income tax on £12,100. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 8_500 + private_pension_income: 1_000 + working_tax_credit_reported: 1 + benunits: + benunit: + members: [pensioner] + households: + household: + members: [pensioner] + output: + tax_credits_current_year_income: 11_800 + working_tax_credit: 858.55 + pension_credit: 0 + +- name: A joint claim has one £300 disregard, across both State Pensions + # Step one is the claimants' aggregate: 8,000 + 7,500 - 300 = £15,200 (not + # less £600). Total: 15,200 + 2,600 = £17,800. + # WTC maximum: 2,435 + 2,500 = £4,935; less 0.41 x (17,800 - 7,955) = + # £4,036.45; so £898.55. (With State Pension in step two: £18,100 and + # £775.55.) + # Pension Credit income is at least 15,500 + 2,600 - 10 x 52 + 898.55 = + # £18,478.55, above the 332.95 x 52 = £17,313.40 couple guarantee. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + pensioner_1: + age: 70 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 8_000 + working_tax_credit_reported: 1 + pensioner_2: + age: 70 + is_claimant_or_partner: true + state_pension: 7_500 + benunits: + benunit: + members: [pensioner_1, pensioner_2] + would_claim_uc: false + households: + household: + members: [pensioner_1, pensioner_2] + output: + tax_credits_current_year_income: 17_800 + working_tax_credit: 898.55 + pension_credit: 0 + +- name: A grandparent's Child Tax Credit is tapered on income with the State Pension in step one + # A grandparent aged 70 responsible for a grandchild aged 10, working 10 + # hours a week (no WTC: a single claimant with a child needs 16), claiming + # CTC only. + # Tax credit income: (11,000 - 300) + 9,500 = £20,200, above the £19,995 + # threshold. CTC: 545 + 3,455 = £4,000, less 0.41 x 205 = £84.05, so + # £3,915.95. (With State Pension in step two: £20,500 and £3,792.95.) + # Pension Credit income is above the guarantee: 11,000 + 9,500 less income + # tax of 0.2 x (20,500 - 12,570) = £1,586. SPC Regs reg 6(6)(d) and (11) + # give no child amount to a tax credit recipient; it is entered because the + # model applies neither. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + grandparent: + age: 70 + is_claimant_or_partner: true + employment_income: 9_500 + weekly_hours: 10 + state_pension: 11_000 + child_tax_credit_reported: 1 + grandchild: + age: 10 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent, grandchild] + would_claim_uc: false + child_minimum_guarantee_addition: 0 + CTC_family_element: 545 + households: + household: + members: [grandparent, grandchild] + output: + tax_credits_current_year_income: 20_200 + child_tax_credit: 3_915.95 + working_tax_credit: 0 + pension_credit: 0 + +- name: Where Pension Credit is paid without the passport, it falls by the WTC gained + # The case with no consistent passport state (see + # tax_credits_pension_credit_passport.yaml), with no savings interest. + # Tax credit income: (6,600 - 300) + 2,600 = £8,900. + # Passport: with the test lifted WTC is £2,435, and Pension Credit income + # 2,340 + 6,600 + 2,435 = £11,375 is above the £11,343.80 guarantee, so the + # income test stays. + # WTC: 2,435 - 0.41 x (8,900 - 7,955) = £2,047.55. Guarantee Credit: + # 11,343.80 - (2,340 + 6,600 + 2,047.55) = £356.25. + # With State Pension in step two, income is £9,200, WTC £1,924.55 and + # Pension Credit £479.25: the £123 more WTC is £123 less Guarantee Credit. + # Pension Credit earnings are entered net of the SPC Regs Sch VI para 5 + # disregard (£5 a week), which the model does not apply. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 6_600 + working_tax_credit_reported: 1 + benunits: + benunit: + members: [pensioner] + pension_credit_earnings: 2_340 + households: + household: + members: [pensioner] + output: + tax_credits_current_year_income: 8_900 + tax_credits_applicable_income: 8_900 + working_tax_credit: 2_047.55 + pension_credit: 356.25 + +- name: The State Pension disregard can bring a family within the childcare tax credit income limit + # England's two-year-old offer: a child whose parent is entitled to "child + # tax credit where that parent is not also entitled to working tax credit + # ... and has an annual gross income not exceeding £16,190" (SI 2014/2147 + # reg 1(2), "eligible child", as at 1 September 2024). + # Tax credit income: (11,490 - 300) + 5,000 = £16,190, within the limit. + # (With State Pension in step two: £16,490, above it.) + # CTC: £4,000, below the £19,995 threshold. Pension Credit income, + # 11,490 + 5,000 less income tax of 0.2 x (16,490 - 12,570) = £784, is above + # the guarantee. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + grandparent: + age: 70 + is_claimant_or_partner: true + employment_income: 5_000 + weekly_hours: 10 + state_pension: 11_490 + child_tax_credit_reported: 1 + grandchild: + age: 2 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent, grandchild] + would_claim_uc: false + child_minimum_guarantee_addition: 0 + CTC_family_element: 545 + working_tax_credit_run_on: false + extended_childcare_entitlement_eligible: false + households: + household: + members: [grandparent, grandchild] + country: ENGLAND + output: + tax_credits_current_year_income: 16_190 + child_tax_credit: 4_000 + pension_credit: 0 + meets_child_tax_credit_criteria_for_targeted_childcare_entitlement: true + targeted_childcare_entitlement_eligible: true + +- name: A State Pension within the £300 is wholly disregarded + # A pensioner whose only State Pension is £200 a year of graduated + # retirement benefit (ITEPA s.577(1)), with no other step-one income: step + # one is nil, and income is the £2,600 of earnings. Pension Credit is not + # claimed, so the income test applies; WTC is the £2,435 maximum. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + pensioner: + age: 70 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 200 + working_tax_credit_reported: 1 + benunits: + benunit: + members: [pensioner] + would_claim_pc: false + households: + household: + members: [pensioner] + output: + tax_credits_current_year_income: 2_600 + tax_credits_applicable_income: 2_600 + working_tax_credit: 2_435 + +- name: Carer's Allowance and contributory ESA stay in step two, with no disregard + # Social security income (reg 7(1)(a)): Carer's Allowance £81.90 x 52 = + # £4,258.80 and contribution-based ESA £5,000; Table 3 disregards neither. + # Step one is nil, so income is 4,258.80 + 5,000 = £9,258.80. + # CTC: £4,000, below the £19,995 threshold. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + carer: + age: 45 + is_claimant_or_partner: true + carers_allowance: 4_258.80 + child_tax_credit_reported: 1 + partner: + age: 45 + is_claimant_or_partner: true + esa_contrib_reported: 5_000 + child: + age: 8 + is_claimant_or_partner: false + benunits: + benunit: + members: [carer, partner, child] + would_claim_uc: false + CTC_family_element: 545 + households: + household: + members: [carer, partner, child] + output: + tax_credits_current_year_income: 9_258.80 + child_tax_credit: 4_000 diff --git a/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py b/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py new file mode 100644 index 0000000000..2e00a3db27 --- /dev/null +++ b/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py @@ -0,0 +1,339 @@ +"""State Pension is step-one pension income for tax credits. + +SI 2002/2006 reg 3(1): Step One adds "the pension income (as defined in +regulation 5(1)), the investment income ..., the property income ..."; "If the +result of this step is £300 or less, it is treated as nil. If the result of +this step is more than £300, only the excess is taken into account". Pension +income includes "any pension to which section 577 ... of ITEPA applies" (reg +5(1)(a)), which is the State Pension (ITEPA 2003 s.577(1)), and reg 7(2) takes +it out of social security income, which is step two. + +Properties, for any family: + +1. Law oracle: tax_credits_current_year_income is the claimants' step-one + income (State Pension, private pension, savings interest, dividends and + property income) less £300, floored at nil, plus their step-two income + (earnings, trading income, Carer's Allowance, Carer Support Payment, + contribution-based JSA and ESA, Incapacity Benefit and miscellaneous + income), computed here directly from the inputs. +2. Against the previous formula, which put the State Pension in step two + (applied below as a reform): income never rises, and falls by exactly + min(State Pension, max(0, £300 - other step-one income)), so by at most + £300. It is unchanged where other step-one income is £300 or more. +3. State Pension is treated exactly as private pension income: moving an + amount from one to the other never changes the income. +4. Awards in 2024-25: WTC and CTC never fall, and together rise by at most + 41% of £300 plus the £26 minimum award. Guarantee Credit, which counts + working tax credit as income, never rises, and falls by no more than the + working tax credit gained. +5. In 2025-26, with no tax credit awards, every award and Pension Credit + equal the previous formula's. +6. Every component of social_security_income is counted exactly once: the + State Pension in step one, the rest in step two. +""" + +import numpy as np +from hypothesis import HealthCheck, event, example, given, settings +from hypothesis import strategies as st +from policyengine_core.reforms import Reform + +from policyengine_uk import Simulation +from policyengine_uk.model_api import * +from policyengine_uk.variables.gov.dwp import tax_credits_current_year_income as tc +from policyengine_uk.variables.gov.hmrc.income_tax.social_security_income import ( + social_security_income, +) + +ACTIVE_YEAR = 2024 # 2024-25, the last year of tax credit awards +INACTIVE_YEAR = 2025 +DISREGARD = 300 +TAPER = 0.41 +MIN_AWARD = 26 +TOL = 0.05 # float32 arithmetic on sums up to about £100,000 +OTHER_STEP_ONE = [ + "private_pension_income", + "savings_interest_income", + "dividend_income", + "property_income", +] +STEP_TWO = [ + "employment_income", + "self_employment_income", + "carers_allowance", + "carer_support_payment", + "jsa_contrib_reported", + "esa_contrib_reported", + "incapacity_benefit_reported", + "miscellaneous_income", +] +AWARDS = ["working_tax_credit", "child_tax_credit", "tax_credits"] +PENSION_CREDIT = ["guarantee_credit", "savings_credit", "pension_credit"] + + +class previous_tax_credits_current_year_income(tc.tax_credits_current_year_income): + # The formula before this change: the State Pension in step two, through + # social_security_income. + def formula(benunit, period, parameters): + person = benunit.members + members = person("is_claimant_or_partner", period) | person( + "is_child_or_qualifying_young_person_for_child_tax_credit", period + ) + TC = parameters(period).gov.dwp.tax_credits + income = add_for_members(benunit, period, OTHER_STEP_ONE, members) + income = max_(income - TC.means_test.non_earned_disregard, 0) + step_two = [ + "employment_income", + "self_employment_income", + "social_security_income", + "miscellaneous_income", + ] + return income + add_for_members(benunit, period, step_two, members) + + +previous_tax_credits_current_year_income.__name__ = "tax_credits_current_year_income" + + +class state_pension_in_step_two(Reform): + def apply(self): + self.update_variable(previous_tax_credits_current_year_income) + + +# Amounts around the £300 disregard, so other step-one income falls below, at +# and above it. +small = st.sampled_from([0, 0, 0, 50, 150, 299.99, 300, 300.01, 1_000, 5_000]) + + +def sometimes(amount): + return st.sampled_from([0, 0, 0, amount]) + + +@st.composite +def adults(draw): + earnings = draw(st.sampled_from([0, 0, 2_600, 6_000, 12_000, 25_000])) + return { + "age": draw(st.integers(25, 90)), + "state_pension": draw( + st.one_of( + st.sampled_from([0, 0, 200, 4_000, 6_600, 9_500, 11_500]), + st.floats(0, 15_000, allow_nan=False, allow_infinity=False), + ) + ), + **{source: draw(small) for source in OTHER_STEP_ONE}, + "employment_income": earnings, + "weekly_hours": draw(st.sampled_from([10, 16, 20, 30])) if earnings else 0, + "self_employment_income": draw(sometimes(3_000)), + "carers_allowance": draw(sometimes(4_258.80)), + "carer_support_payment": draw(sometimes(4_258.80)), + "jsa_contrib_reported": draw(sometimes(3_000)), + "esa_contrib_reported": draw(sometimes(5_000)), + "incapacity_benefit_reported": draw(sometimes(4_000)), + "miscellaneous_income": draw(sometimes(500)), + "working_tax_credit_reported": draw(st.sampled_from([0, 1])), + "child_tax_credit_reported": draw(st.sampled_from([0, 1])), + } + + +@st.composite +def families(draw): + members = [draw(adults())] + if draw(st.booleans()): + members.append(draw(adults())) + children = [ + {"age": draw(st.integers(0, 15))} for _ in range(draw(st.integers(0, 2))) + ] + benunit = {"would_claim_pc": draw(st.sampled_from([True, True, False]))} + household = {"savings": draw(st.sampled_from([0, 5_000, 12_000]))} + return members, children, benunit, household + + +def situation(units): + def at(value): + return {year: value for year in (ACTIVE_YEAR, INACTIVE_YEAR)} + + people, benunits, households = {}, {}, {} + for i, (members, children, benunit, household) in enumerate(units): + names = [] + for j, inputs in enumerate(members): + name = f"a{i}_{j}" + people[name] = {k: at(v) for k, v in inputs.items()} + people[name]["is_claimant_or_partner"] = at(True) + names.append(name) + for j, inputs in enumerate(children): + name = f"c{i}_{j}" + people[name] = {k: at(v) for k, v in inputs.items()} + people[name]["is_claimant_or_partner"] = at(False) + names.append(name) + benunits[f"b{i}"] = { + "members": names, + "would_claim_uc": at(False), + **{k: at(v) for k, v in benunit.items()}, + } + households[f"h{i}"] = { + "members": names, + **{k: at(v) for k, v in household.items()}, + } + return {"people": people, "benunits": benunits, "households": households} + + +def law(members): + """Reg 3(1) steps one and two, from the claimants' inputs.""" + state_pension = sum(m["state_pension"] for m in members) + other_step_one = sum(m[s] for m in members for s in OTHER_STEP_ONE) + step_two = sum(m[s] for m in members for s in STEP_TWO) + income = max(state_pension + other_step_one - DISREGARD, 0) + step_two + # The previous formula's loss of the disregard on State Pension. + change = min(state_pension, max(0, DISREGARD - other_step_one)) + return income, change + + +def moved_to_private_pension(unit): + members, children, benunit, household = unit + moved = [ + { + **m, + "state_pension": 0, + "private_pension_income": m["private_pension_income"] + m["state_pension"], + } + for m in members + ] + return moved, children, benunit, household + + +def calculate(sim, variables, year): + return {v: np.asarray(sim.calculate(v, year)) for v in variables} + + +SETTINGS = settings( + max_examples=25, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + +# The YAML households (tax_credits_state_pension_step_one.yaml), run as +# examples: State Pension wholly, partly and not at all within the £300, and +# the case where Pension Credit is paid without the passport. +PENSIONER = { + **{k: 0 for k in ["state_pension", *OTHER_STEP_ONE, *STEP_TWO]}, + "age": 70, + "employment_income": 2_600, + "weekly_hours": 20, + "working_tax_credit_reported": 1, + "child_tax_credit_reported": 0, +} +YAML_FAMILIES = [ + [ + ( + [{**PENSIONER, "state_pension": 9_500}], + [], + {"would_claim_pc": True}, + {"savings": 0}, + ) + ], + [ + ( + [{**PENSIONER, "state_pension": 9_500, "savings_interest_income": 100}], + [], + {"would_claim_pc": True}, + {"savings": 0}, + ) + ], + [ + ( + [{**PENSIONER, "state_pension": 8_500, "private_pension_income": 1_000}], + [], + {"would_claim_pc": True}, + {"savings": 0}, + ) + ], + [ + ( + [{**PENSIONER, "state_pension": 6_600}], + [], + {"would_claim_pc": True}, + {"savings": 0}, + ) + ], + [ + ( + [{**PENSIONER, "state_pension": 200}], + [], + {"would_claim_pc": False}, + {"savings": 0}, + ) + ], +] + + +@SETTINGS +@given(st.lists(families(), min_size=1, max_size=6)) +@example(YAML_FAMILIES[0]) +@example(YAML_FAMILIES[1]) +@example(YAML_FAMILIES[2]) +@example(YAML_FAMILIES[3]) +@example(YAML_FAMILIES[4]) +def test_state_pension_is_step_one_income(units): + n = len(units) + sit = situation(units + [moved_to_private_pension(u) for u in units]) + new = Simulation(situation=sit) + old = Simulation(situation=sit) + old.apply_reform(state_pension_in_step_two) + + income = "tax_credits_current_year_income" + outputs = [income, *AWARDS, *PENSION_CREDIT] + new_values = calculate(new, outputs, ACTIVE_YEAR) + old_values = calculate(old, outputs, ACTIVE_YEAR) + new_inactive = calculate(new, outputs, INACTIVE_YEAR) + old_inactive = calculate(old, outputs, INACTIVE_YEAR) + + def gain(v): + return new_values[v][:n] - old_values[v][:n] + + for i, (members, *_rest) in enumerate(units): + expected, change = law(members) + context = (units[i], {k: v[i] for k, v in new_values.items()}) + # 1. The law, computed from the inputs. + assert abs(new_values[income][i] - expected) < TOL, context + # 2. Against the previous formula. + assert abs(old_values[income][i] - new_values[income][i] - change) < TOL, ( + context + ) + assert 0 <= change <= DISREGARD + # 3. State Pension is treated as private pension income. + assert abs(new_values[income][n + i] - new_values[income][i]) < TOL, context + # 5. No awards from 2025-26: only the income figure can differ. + for v in [*AWARDS, *PENSION_CREDIT]: + assert abs(new_inactive[v][i] - old_inactive[v][i]) < TOL, (v, context) + assert new_inactive["tax_credits"][i] == 0 + assert abs(old_inactive[income][i] - new_inactive[income][i] - change) < TOL + + # 4. Awards in 2024-25. + for v in ["working_tax_credit", "child_tax_credit", "tax_credits"]: + assert (gain(v) > -TOL).all(), (v, units) + assert (gain("tax_credits") <= TAPER * DISREGARD + MIN_AWARD + TOL).all(), units + assert (gain("guarantee_credit") < TOL).all(), units + assert (-gain("guarantee_credit") <= gain("working_tax_credit") + TOL).all(), units + + # Coverage (pytest --hypothesis-show-statistics). + for i, (members, *_rest) in enumerate(units): + change = law(members)[1] + event( + "income change: " + + ("none" if change == 0 else "£300" if change == DISREGARD else "partial") + ) + for v in ["working_tax_credit", "child_tax_credit"]: + if gain(v)[i] > TOL: + event(f"{v} raised") + if gain("guarantee_credit")[i] < -TOL: + event("guarantee_credit lowered") + + +def test_social_security_income_is_counted_once(): + # Reg 7(2): the State Pension is pension income, not social security + # income. Every other component of the model's social_security_income is + # reg 7 social security income. A new component must be classified here. + components = set(social_security_income.adds) + assert "state_pension" in tc.STEP_ONE_INCOME + assert "state_pension" not in tc.SOCIAL_SECURITY_INCOME + assert components == set(tc.SOCIAL_SECURITY_INCOME) | {"state_pension"} + assert not set(tc.STEP_ONE_INCOME) & set(tc.SOCIAL_SECURITY_INCOME) diff --git a/policyengine_uk/variables/gov/dwp/tax_credits_current_year_income.py b/policyengine_uk/variables/gov/dwp/tax_credits_current_year_income.py index cbddd40cfc..5678bfbe28 100644 --- a/policyengine_uk/variables/gov/dwp/tax_credits_current_year_income.py +++ b/policyengine_uk/variables/gov/dwp/tax_credits_current_year_income.py @@ -1,5 +1,42 @@ from policyengine_uk.model_api import * +# Step one of SI 2002/2006 reg 3(1): pension income (reg 5), investment income +# (reg 10), property income (reg 11), foreign income (reg 12) and notional +# income (reg 13). Pension income includes "any pension to which section 577 +# ... of ITEPA applies" (reg 5(1)(a)), and ITEPA 2003 s.577(1) applies to "the +# state pension", so the State Pension is step one, with the £300 disregard. +# private_pension_income is pension income (reg 5(1)(b), (d): ITEPA ss.569, +# 579A) or, for a foreign pension, foreign income (reg 12), which is also step +# one. savings_interest_income and dividend_income are investment income (reg +# 10(1)(a), (d)), and property_income is property income. +STEP_ONE_INCOME = [ + "state_pension", + "private_pension_income", + "savings_interest_income", + "dividend_income", + "property_income", +] + +# Social security income (reg 7): benefits payable under the Contributions and +# Benefits Act, the Jobseekers Act 1995 and Part 1 of the Welfare Reform Act +# 2007 (reg 7(1)(a)), and Carer Support Payment, made under s.28 of the Social +# Security (Scotland) Act 2018 (reg 7(1)(ab), from 15 March 2023). It is the +# model's social_security_income without the State Pension: "Pensions under the +# Contributions and Benefits Act which are pension income by virtue of +# regulation 5(1)(a) are not social security income" (reg 7(2)). The model's +# other benefits are disregarded by reg 7(3) Table 3, or are not under an Act +# reg 7(1) lists (Pension Credit, Universal Credit). Two partial disregards +# are not modelled: short-term lower-rate Incapacity Benefit and Incapacity +# Benefit after Invalidity Benefit (item 14), and contribution-based JSA above +# the ITEPA s.674 taxable maximum (item 16). +SOCIAL_SECURITY_INCOME = [ + "incapacity_benefit", + "jsa_contrib_reported", + "esa_contrib_reported", + "carers_allowance", + "carer_support_payment", +] + class tax_credits_current_year_income(Variable): value_type = float @@ -18,6 +55,9 @@ class tax_credits_current_year_income(Variable): reference = ( "https://www.legislation.gov.uk/ukpga/2002/21/section/7", "https://www.legislation.gov.uk/uksi/2002/2006/regulation/3", + "https://www.legislation.gov.uk/uksi/2002/2006/regulation/5", + "https://www.legislation.gov.uk/uksi/2002/2006/regulation/7", + "https://www.legislation.gov.uk/ukpga/2003/1/section/577", ) def formula(benunit, period, parameters): @@ -30,18 +70,19 @@ def formula(benunit, period, parameters): "is_child_or_qualifying_young_person_for_child_tax_credit", period ) TC = parameters(period).gov.dwp.tax_credits - STEP_1_COMPONENTS = [ - "private_pension_income", - "savings_interest_income", - "dividend_income", - "property_income", - ] - income = add_for_members(benunit, period, STEP_1_COMPONENTS, members) + income = add_for_members(benunit, period, STEP_ONE_INCOME, members) + # "If the result of this step is £300 or less, it is treated as nil. If + # the result of this step is more than £300, only the excess is taken + # into account in the following steps." The £300 is per claim, not per + # joint claimant. income = max_(income - TC.means_test.non_earned_disregard, 0) + # Step two: employment income (reg 4), social security income (reg 7) + # and miscellaneous income (reg 18). Trading income is step four, which + # adds it to the same total. STEP_2_COMPONENTS = [ "employment_income", "self_employment_income", - "social_security_income", + *SOCIAL_SECURITY_INCOME, "miscellaneous_income", ] bi = parameters(period).gov.contrib.ubi_center.basic_income From 87068e4998d144171eccf4d315c6c0cc2ea76271 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 07:43:37 -0400 Subject: [PATCH 2/5] Draw more families with no other step-one income Half the drawn adults now have no private pension, interest, dividends or property income, where the State Pension disregard changes the income. Co-Authored-By: Claude Opus 5.5 --- .../test_tax_credits_state_pension_step_one_properties.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py b/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py index 2e00a3db27..3aa316f160 100644 --- a/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py +++ b/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py @@ -110,6 +110,8 @@ def sometimes(amount): @st.composite def adults(draw): earnings = draw(st.sampled_from([0, 0, 2_600, 6_000, 12_000, 25_000])) + # Half the adults have no other step-one income, where the change bites. + other_step_one = draw(st.booleans()) return { "age": draw(st.integers(25, 90)), "state_pension": draw( @@ -118,7 +120,7 @@ def adults(draw): st.floats(0, 15_000, allow_nan=False, allow_infinity=False), ) ), - **{source: draw(small) for source in OTHER_STEP_ONE}, + **{s: draw(small) if other_step_one else 0 for s in OTHER_STEP_ONE}, "employment_income": earnings, "weekly_hours": draw(st.sampled_from([10, 16, 20, 30])) if earnings else 0, "self_employment_income": draw(sometimes(3_000)), From 3008a29208029be97868e1f7b03a8777cf69c2cb Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 23 Oct 2026 03:38:30 -0400 Subject: [PATCH 3/5] Enter the passport fixture's earnings gross so the PC disregard applies once MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "Pension Credit is paid without the passport" case supplied pension_credit_earnings of £2,340, already net of the SPC Regs 2002 Sch VI para 5 disregard (£5 a week). Main now deducts that disregard from the claimant's £2,600 employment income (pension_credit_earnings_disregard, read by pension_credit_income), so it came off twice: Pension Credit income with the test lifted fell to £11,115, below the £11,343.80 guarantee, and the passport set tax credit applicable income to nil. The override is removed, as tax_credits_pension_credit_passport.yaml already requires, and the case pins the £260 disregard and £10,987.55 Pension Credit income; its outputs are unchanged. Co-Authored-By: Claude Opus 5.5 --- .../gov/dwp/tax_credits_state_pension_step_one.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml index ea3ce3d8fd..1e09248351 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml @@ -195,6 +195,9 @@ # The case with no consistent passport state (see # tax_credits_pension_credit_passport.yaml), with no savings interest. # Tax credit income: (6,600 - 300) + 2,600 = £8,900. + # Employment income is entered gross. Pension Credit counts it less the + # SPC Regs 2002 Sch VI para 5 disregard of £5 a week, which the model + # deducts once: 2,600 - 260 = £2,340. # Passport: with the test lifted WTC is £2,435, and Pension Credit income # 2,340 + 6,600 + 2,435 = £11,375 is above the £11,343.80 guarantee, so the # income test stays. @@ -202,8 +205,6 @@ # 11,343.80 - (2,340 + 6,600 + 2,047.55) = £356.25. # With State Pension in step two, income is £9,200, WTC £1,924.55 and # Pension Credit £479.25: the £123 more WTC is £123 less Guarantee Credit. - # Pension Credit earnings are entered net of the SPC Regs Sch VI para 5 - # disregard (£5 a week), which the model does not apply. period: 2024 absolute_error_margin: 0.01 input: @@ -218,7 +219,6 @@ benunits: benunit: members: [pensioner] - pension_credit_earnings: 2_340 households: household: members: [pensioner] @@ -226,6 +226,8 @@ tax_credits_current_year_income: 8_900 tax_credits_applicable_income: 8_900 working_tax_credit: 2_047.55 + pension_credit_earnings_disregard: 260 + pension_credit_income: 10_987.55 pension_credit: 356.25 - name: The State Pension disregard can bring a family within the childcare tax credit income limit From 422601af8bdb73789edb66a88d8fc90641e3b112 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 24 Oct 2026 03:07:19 -0400 Subject: [PATCH 4/5] Count Widowed Parent's Allowance as step-one tax credit income MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main (a1ff54241) added widowed_parents_allowance to social_security_income, so this branch's classification test failed in the merge queue: a new component must be classified. ITEPA 2003 s.577(1) applies to "widowed parent's allowance" as it does to the State Pension, so it is pension income under SI 2002/2006 reg 5(1)(a) and reg 7(2) takes it out of social security income. It is now step one, sharing the £300 disregard, with a hand-computed YAML case. Co-Authored-By: Claude Opus 5.5 --- ...ax-credits-state-pension-step-one.fixed.md | 2 +- .../tax_credits_state_pension_step_one.yaml | 27 +++++++++++++++++++ ...edits_state_pension_step_one_properties.py | 22 ++++++++++----- .../dwp/tax_credits_current_year_income.py | 11 +++++--- 4 files changed, 50 insertions(+), 12 deletions(-) diff --git a/changelog.d/tax-credits-state-pension-step-one.fixed.md b/changelog.d/tax-credits-state-pension-step-one.fixed.md index 6f44150c05..fcbc993991 100644 --- a/changelog.d/tax-credits-state-pension-step-one.fixed.md +++ b/changelog.d/tax-credits-state-pension-step-one.fixed.md @@ -1 +1 @@ -Count the State Pension as step-one pension income in tax credit income, so the £300 disregard on step-one income applies to it (SI 2002/2006 regs 3(1), 5(1)(a) and 7(2); ITEPA 2003 s.577). +Count the State Pension and Widowed Parent's Allowance as step-one pension income in tax credit income, so the £300 disregard on step-one income applies to them (SI 2002/2006 regs 3(1), 5(1)(a) and 7(2); ITEPA 2003 s.577). diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml index 1e09248351..653c860781 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_state_pension_step_one.yaml @@ -333,3 +333,30 @@ output: tax_credits_current_year_income: 9_258.80 child_tax_credit: 4_000 + +- name: Widowed Parent's Allowance is step one, sharing the £300 disregard + # ITEPA 2003 s.577(1) applies to "widowed parent's allowance", so it is + # pension income (reg 5(1)(a)) and not social security income (reg 7(2)). + # Step one: 7,000 + 100 savings interest - 300 = £6,800; no step-two income. + # (In step two it would count in full: 7,000 + max(0, 100 - 300) = £7,000.) + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_claimant_or_partner: true + widowed_parents_allowance_reported: 7_000 + savings_interest_income: 100 + child: + age: 3 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, child] + households: + household: + members: [parent, child] + output: + widowed_parents_allowance: [7_000, 0] + tax_credits_current_year_income: 6_800 diff --git a/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py b/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py index 38ac02bab3..93f6b67915 100644 --- a/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py +++ b/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py @@ -29,7 +29,8 @@ 5. In 2025-26, with no tax credit awards, every award and Pension Credit equal the previous formula's. 6. Every component of social_security_income is counted exactly once: the - State Pension in step one, the rest in step two. + State Pension and Widowed Parent's Allowance (both ITEPA s.577 pensions) in + step one, the rest in step two. """ import numpy as np @@ -346,14 +347,21 @@ def gain(v): event("guarantee_credit lowered") +# The components of social_security_income to which ITEPA 2003 s.577(1) +# applies: pension income by reg 5(1)(a), so not social security income (reg +# 7(2)). +ITEPA_S577_PENSIONS = {"state_pension", "widowed_parents_allowance"} + + def test_social_security_income_is_counted_once(): - # Reg 7(2): the State Pension is pension income, not social security - # income. Every other component of the model's social_security_income is - # reg 7 social security income. A new component must be classified here. + # Reg 7(2): the State Pension and Widowed Parent's Allowance are pension + # income, not social security income. Every other component of the model's + # social_security_income is reg 7 social security income. A new component + # must be classified here. components = set(social_security_income.adds) - assert "state_pension" in tc.STEP_ONE_INCOME - assert "state_pension" not in tc.SOCIAL_SECURITY_INCOME - assert components == set(tc.SOCIAL_SECURITY_INCOME) | {"state_pension"} + assert ITEPA_S577_PENSIONS <= set(tc.STEP_ONE_INCOME) + assert not ITEPA_S577_PENSIONS & set(tc.SOCIAL_SECURITY_INCOME) + assert components == set(tc.SOCIAL_SECURITY_INCOME) | ITEPA_S577_PENSIONS assert not set(tc.STEP_ONE_INCOME) & set(tc.SOCIAL_SECURITY_INCOME) diff --git a/policyengine_uk/variables/gov/dwp/tax_credits_current_year_income.py b/policyengine_uk/variables/gov/dwp/tax_credits_current_year_income.py index 733031fa8a..801ce96fbf 100644 --- a/policyengine_uk/variables/gov/dwp/tax_credits_current_year_income.py +++ b/policyengine_uk/variables/gov/dwp/tax_credits_current_year_income.py @@ -4,7 +4,8 @@ # (reg 10), property income (reg 11), foreign income (reg 12) and notional # income (reg 13). Pension income includes "any pension to which section 577 # ... of ITEPA applies" (reg 5(1)(a)), and ITEPA 2003 s.577(1) applies to "the -# state pension", so the State Pension is step one, with the £300 disregard. +# state pension" and "widowed parent's allowance", so both are step one, with +# the £300 disregard. # private_pension_income is pension income (reg 5(1)(b), (d): ITEPA ss.569, # 579A) or, for a foreign pension, foreign income (reg 12), which is also step # one. savings_interest_income and dividend_income are investment income (reg @@ -12,6 +13,7 @@ # model's deduction of property finance costs. STEP_ONE_INCOME = [ "state_pension", + "widowed_parents_allowance", "private_pension_income", "savings_interest_income", "dividend_income", @@ -23,9 +25,10 @@ # 2007 (reg 7(1)(a)), and amounts under regulations made under s.28 of the # Social Security (Scotland) Act 2018 (reg 7(1)(ab), from 15 March 2023): # Carer Support Payment and the Scottish Carer Supplement component. It is the -# model's social_security_income without the State Pension: "Pensions under the -# Contributions and Benefits Act which are pension income by virtue of -# regulation 5(1)(a) are not social security income" (reg 7(2)). The model's +# model's social_security_income without the State Pension and Widowed +# Parent's Allowance (SSCBA 1992 s.39A): "Pensions under the Contributions and +# Benefits Act which are pension income by virtue of regulation 5(1)(a) are not +# social security income" (reg 7(2)). The model's # other benefits are disregarded by reg 7(3) Table 3, or are not under an Act # reg 7(1) lists (Pension Credit, Universal Credit). Two partial disregards # are not modelled: short-term lower-rate Incapacity Benefit and Incapacity From 364e135f1c682684a246138d94ef554e0601e336 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 24 Oct 2026 03:20:04 -0400 Subject: [PATCH 5/5] Draw Widowed Parent's Allowance in the step-one property tests The review's P3: generated adults under State Pension age now sometimes receive WPA, the law oracle counts it in step one with the State Pension, property 2's reduction is min(State Pension + WPA, max(0, 300 - other step-one income)), and property 3 moves both s.577 pensions to private pension. A Hypothesis event shows WPA in 40% of the generated cases. Co-Authored-By: Claude Opus 5.5 --- ...edits_state_pension_step_one_properties.py | 60 +++++++++++-------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py b/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py index 93f6b67915..bf868f40c3 100644 --- a/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py +++ b/policyengine_uk/tests/test_tax_credits_state_pension_step_one_properties.py @@ -11,17 +11,19 @@ Properties, for any family: 1. Law oracle: tax_credits_current_year_income is the claimants' step-one - income (State Pension, private pension, savings interest, dividends and - property income) less £300, floored at nil, plus their step-two income + income (State Pension, Widowed Parent's Allowance, private pension, savings + interest, dividends and property income) less £300, floored at nil, plus + their step-two income (earnings, trading income, Carer's Allowance, Carer Support Payment, contribution-based JSA and ESA, Incapacity Benefit and miscellaneous income), computed here directly from the inputs. -2. Against the previous formula, which put the State Pension in step two - (applied below as a reform): income never rises, and falls by exactly - min(State Pension, max(0, £300 - other step-one income)), so by at most - £300. It is unchanged where other step-one income is £300 or more. -3. State Pension is treated exactly as private pension income: moving an - amount from one to the other never changes the income. +2. Against the previous formula, which put the State Pension and Widowed + Parent's Allowance in step two (applied below as a reform): income never + rises, and falls by exactly min(State Pension + WPA, max(0, £300 - other + step-one income)), so by at most £300. It is unchanged where other step-one + income is £300 or more. +3. The State Pension and WPA are treated exactly as private pension income: + moving an amount from either to private pension never changes the income. 4. Awards in 2024-25: WTC and CTC never fall, and together rise by at most 41% of £300 plus the £26 minimum award. Guarantee Credit, which counts working tax credit as income, never rises, and falls by no more than the @@ -45,6 +47,11 @@ social_security_income, ) +# The components of social_security_income to which ITEPA 2003 s.577(1) +# applies: pension income by reg 5(1)(a), so not social security income (reg +# 7(2)). The second list is the inputs the generated adults supply for them. +ITEPA_S577_PENSIONS = {"state_pension", "widowed_parents_allowance"} +S577_PENSION_INPUTS = ["state_pension", "widowed_parents_allowance_reported"] ACTIVE_YEAR = 2024 # 2024-25, the last year of tax credit awards INACTIVE_YEAR = 2025 DISREGARD = 300 @@ -72,9 +79,9 @@ class previous_tax_credits_current_year_income(tc.tax_credits_current_year_income): - # The formula before this change: the State Pension in step two, through - # social_security_income. Keep main's claimant scope, net property income - # and trading losses, so only the State Pension classification differs. + # The formula before this change: the State Pension and WPA in step two, + # through social_security_income. Keep main's claimant scope, net property + # income and trading losses, so only their classification differs. def formula(benunit, period, parameters): person = benunit.members members = person("is_claimant_or_partner", period) @@ -124,8 +131,12 @@ def adults(draw): earnings = draw(st.sampled_from([0, 0, 2_600, 6_000, 12_000, 25_000])) # Half the adults have no other step-one income, where the change bites. other_step_one = draw(st.booleans()) + age = draw(st.integers(25, 90)) return { - "age": draw(st.integers(25, 90)), + "age": age, + # Widowed Parent's Allowance stops at State Pension age (SSCBA 1992 + # s.39A(4)); the model takes the reported amount as given. + "widowed_parents_allowance_reported": draw(sometimes(7_000)) if age < 66 else 0, "state_pension": draw( st.one_of( st.sampled_from([0, 0, 200, 4_000, 6_600, 9_500, 11_500]), @@ -196,12 +207,12 @@ def law(members): their property income is already the amount counted by the means test. The separate preservation regression covers those deductions. """ - state_pension = sum(m["state_pension"] for m in members) + pensions = sum(m[s] for m in members for s in S577_PENSION_INPUTS) other_step_one = sum(m[s] for m in members for s in OTHER_STEP_ONE) step_two = sum(m[s] for m in members for s in STEP_TWO) - income = max(state_pension + other_step_one - DISREGARD, 0) + step_two - # The previous formula's loss of the disregard on State Pension. - change = min(state_pension, max(0, DISREGARD - other_step_one)) + income = max(pensions + other_step_one - DISREGARD, 0) + step_two + # The previous formula's loss of the disregard on these pensions. + change = min(pensions, max(0, DISREGARD - other_step_one)) return income, change @@ -210,8 +221,9 @@ def moved_to_private_pension(unit): moved = [ { **m, - "state_pension": 0, - "private_pension_income": m["private_pension_income"] + m["state_pension"], + **{s: 0 for s in S577_PENSION_INPUTS}, + "private_pension_income": m["private_pension_income"] + + sum(m[s] for s in S577_PENSION_INPUTS), } for m in members ] @@ -233,7 +245,7 @@ def calculate(sim, variables, year): # examples: State Pension wholly, partly and not at all within the £300, and # the case where Pension Credit is paid without the passport. PENSIONER = { - **{k: 0 for k in ["state_pension", *OTHER_STEP_ONE, *STEP_TWO]}, + **{k: 0 for k in [*S577_PENSION_INPUTS, *OTHER_STEP_ONE, *STEP_TWO]}, "age": 70, "employment_income": 2_600, "weekly_hours": 20, @@ -318,7 +330,7 @@ def gain(v): context ) assert 0 <= change <= DISREGARD - # 3. State Pension is treated as private pension income. + # 3. The s.577 pensions are treated as private pension income. assert abs(new_values[income][n + i] - new_values[income][i]) < TOL, context # 5. No awards from 2025-26: only the income figure can differ. for v in [*AWARDS, *PENSION_CREDIT]: @@ -340,6 +352,8 @@ def gain(v): "income change: " + ("none" if change == 0 else "£300" if change == DISREGARD else "partial") ) + if any(m["widowed_parents_allowance_reported"] for m in members): + event("widowed parent's allowance") for v in ["working_tax_credit", "child_tax_credit"]: if gain(v)[i] > TOL: event(f"{v} raised") @@ -347,12 +361,6 @@ def gain(v): event("guarantee_credit lowered") -# The components of social_security_income to which ITEPA 2003 s.577(1) -# applies: pension income by reg 5(1)(a), so not social security income (reg -# 7(2)). -ITEPA_S577_PENSIONS = {"state_pension", "widowed_parents_allowance"} - - def test_social_security_income_is_counted_once(): # Reg 7(2): the State Pension and Widowed Parent's Allowance are pension # income, not social security income. Every other component of the model's