From a27a539ce6fc6bc4910326960dcbd5cb97ceb8ec Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 07:52:06 -0400 Subject: [PATCH 1/5] Pay no Pension Credit child amount with a tax credit award SPC Regs 2002 reg 6(6)(d) applies Schedule IIA "except where paragraph (11) applies", and reg 6(11) applies to "a person who is awarded, or who is treated as having an award of, a tax credit": child tax credit or working tax credit (reg 6(17)). The model paid the Schedule IIA amount alongside the tax credit award. has_tax_credit_award is the award: a claim plus the conditions of entitlement other than the income test, since HMRC may award at a nil rate (TCA 2002 s.14(3)). It reads nothing from Pension Credit, so it adds no dependency cycle with the tax credit passport (SI 2002/2008 reg 4(1)(d)). No awards from 2025-26, when tax credits ended. Schedule IIA began on 1 February 2019 (SI 2018/676 reg 2), but the model paid it from 2015-16: after fiscal-year conversion the rate parameters hold the 2019 rates for 2015 to 2018. The new parameter gov.dwp.pension_credit.guarantee_credit.child.in_effect dates it. The tax credit passport tests no longer enter the child amount as 0. Co-Authored-By: Claude Opus 5.5 --- .../pc-child-amount-tax-credit-award.fixed.md | 2 + .../guarantee_credit/child/in_effect.yaml | 15 + .../child_amount_tax_credit_award.yaml | 329 ++++++++++++++++++ .../tax_credits_pension_credit_passport.yaml | 6 +- ...edit_child_amount_tax_credit_properties.py | 301 ++++++++++++++++ ...dits_pension_credit_passport_properties.py | 5 +- .../variables/gov/dwp/has_tax_credit_award.py | 35 ++ .../child_minimum_guarantee_addition.py | 23 +- 8 files changed, 706 insertions(+), 10 deletions(-) create mode 100644 changelog.d/pc-child-amount-tax-credit-award.fixed.md create mode 100644 policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/child/in_effect.yaml create mode 100644 policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml create mode 100644 policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py create mode 100644 policyengine_uk/variables/gov/dwp/has_tax_credit_award.py diff --git a/changelog.d/pc-child-amount-tax-credit-award.fixed.md b/changelog.d/pc-child-amount-tax-credit-award.fixed.md new file mode 100644 index 0000000000..445c489458 --- /dev/null +++ b/changelog.d/pc-child-amount-tax-credit-award.fixed.md @@ -0,0 +1,2 @@ +- Pay no Pension Credit child amount (SPC Regs 2002 Schedule IIA) to a family awarded child tax credit or working tax credit, including an award at a nil rate (reg 6(6)(d) and (11); TCA 2002 s.14(3)). The new variable `has_tax_credit_award` records the award. +- Pay no Schedule IIA amount before it began on 1 February 2019 (SI 2018/676 reg 2). The new parameter `gov.dwp.pension_credit.guarantee_credit.child.in_effect` dates it. diff --git a/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/child/in_effect.yaml b/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/child/in_effect.yaml new file mode 100644 index 0000000000..f8cca78047 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/pension_credit/guarantee_credit/child/in_effect.yaml @@ -0,0 +1,15 @@ +description: Whether the Pension Credit minimum guarantee includes the Schedule IIA additional amount for a child or qualifying young person. +values: + 2003-10-06: false + # SI 2018/676 reg 2(2)(c) and (4) added reg 6(6)(d) and Schedule IIA from + # 1 February 2019. Before then, support for a pensioner's child came through + # Child Tax Credit. + 2019-02-01: true +metadata: + unit: bool + label: Pension Credit child amount in effect + reference: + - title: The State Pension Credit Regulations 2002 reg. 6(6)(d) + href: https://www.legislation.gov.uk/uksi/2002/1792/regulation/6 + - title: The State Pension Credit (Additional Amount for Child or Qualifying Young Person) (Amendment) Regulations 2018 reg. 2 + href: https://www.legislation.gov.uk/uksi/2018/676/regulation/2 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml new file mode 100644 index 0000000000..5c1ecc1457 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml @@ -0,0 +1,329 @@ +# No Schedule IIA child amount for a tax credit award. +# +# SPC Regs 2002 reg 6(6)(d) adds Schedule IIA's amount for a child or +# qualifying young person "except where paragraph (11) applies". Reg 6(11): +# "This paragraph applies in the case of a person who is awarded, or who is +# treated as having an award of, a tax credit under the Tax Credits Act." Reg +# 6(17): "a tax credit" includes a child tax credit and a working tax credit. +# Reg 6(6)(d), (11)-(17) and Schedule IIA came into force on 1 February 2019 +# (SI 2018/676 reg 2); the model takes a year's rules at the start of the +# fiscal year, so they apply from 2019-20. +# +# An award need not be positive: HMRC may award a tax credit at a nil rate +# (TCA 2002 s.14(3)). The model treats a family as awarded a tax credit when +# it claims one and meets its conditions other than the income test. Tax +# credits ended on 5 April 2025. +# +# Rates (DWP, Benefit and pension rates): +# - 2024-25: standard minimum guarantee £332.95 a week for a couple; Schedule +# IIA £76.79 for the eldest child born before 6 April 2017 (para 10). +# - 2025-26: £346.60 couple; £78.10 eldest child born before 6 April 2017. +# - 2019-20: £255.25 couple; £63.84 eldest child. 2018-19: £248.80 couple. +# - WTC 2024-25: basic element £2,435, couple element £2,500; threshold +# £7,955; withdrawal rate 41%. CTC child element £3,455; family element £545 +# (entered: the model CPI-uprates it past its frozen £545). +# WEEKS_IN_YEAR is 52. +# +# Each couple is aged 70, so both reached pensionable age after 6 April 2016 +# and there is no savings credit (SPCA 2002 s.3(1)(a)). The grandchild is 10, +# born before 6 April 2017. Pension Credit earnings are entered net of the SPC +# Regs Sch VI para 5 disregard (£10 a week for a couple), which the model does +# not apply. £10,000 of savings gives no Pension Credit tariff income, and +# £300 of interest uses up the £300 step-one disregard of tax credit income +# (SI 2002/2006 reg 3). No one pays income tax or National Insurance. + +- name: A Pension Credit couple awarded CTC and WTC gets no child amount + # Tax credit income: State Pension 8,000 + interest (300 - 300) + earnings + # 2,600 = £10,600. The older partner is over 60 and works 20 hours, which + # meets the WTC hours condition (WTC Regs 2002 reg 4(1), second condition). + # The family claims both credits, so it is awarded a tax credit and reg + # 6(11) removes the Schedule IIA amount. Minimum guarantee: 332.95 x 52 = + # £17,313.40. + # With the income test lifted (SI 2002/2008 reg 4(1)(d)): WTC 2,435 + 2,500 + # = £4,935, CTC 545 + 3,455 = £4,000. Pension Credit income: 8,000 + 2,080 + # + 4,935 = £15,015 (CTC is not Pension Credit income), so Guarantee Credit + # is 17,313.40 - 15,015 = £2,298.40 and the passport holds. + # (Paying the child amount as well would give 2,298.40 + 76.79 x 52 = + # £6,291.48.) + period: 2024 + absolute_error_margin: 0.01 + input: + people: + grandparent_1: + age: 70 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 4_000 + savings_interest_income: 300 + child_tax_credit_reported: 1 + working_tax_credit_reported: 1 + grandparent_2: + age: 70 + is_claimant_or_partner: true + state_pension: 4_000 + grandchild: + age: 10 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent_1, grandparent_2, grandchild] + would_claim_uc: false + pension_credit_earnings: 2_080 # 2,600 - 10 x 52 + CTC_family_element: 545 + households: + household: + members: [grandparent_1, grandparent_2, grandchild] + savings: 10_000 + output: + has_tax_credit_award: true + child_minimum_guarantee_addition: 0 + minimum_guarantee: 17_313.40 + tax_credits_applicable_income: 0 + working_tax_credit: 4_935 + child_tax_credit: 4_000 + guarantee_credit: 2_298.40 + pension_credit: 2_298.40 + +- name: The same couple with no tax credit claim gets the child amount + # Schedule IIA para 10: 76.79 x 52 = £3,993.08. Minimum guarantee: + # 17,313.40 + 3,993.08 = £21,306.48. Pension Credit income: 8,000 + 2,080 = + # £10,080, so Guarantee Credit is £11,226.48. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + grandparent_1: + age: 70 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 4_000 + savings_interest_income: 300 + grandparent_2: + age: 70 + is_claimant_or_partner: true + state_pension: 4_000 + grandchild: + age: 10 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent_1, grandparent_2, grandchild] + would_claim_uc: false + pension_credit_earnings: 2_080 + households: + household: + members: [grandparent_1, grandparent_2, grandchild] + savings: 10_000 + output: + has_tax_credit_award: false + child_minimum_guarantee_addition: 3_993.08 + minimum_guarantee: 21_306.48 + working_tax_credit: 0 + child_tax_credit: 0 + guarantee_credit: 11_226.48 + pension_credit: 11_226.48 + +- name: A WTC award alone also removes the child amount + # Reg 6(17): "a tax credit" includes a working tax credit. The first case's + # couple claiming WTC but not CTC: no Schedule IIA amount; the passported + # WTC of £4,935 leaves Guarantee Credit of £2,298.40, as before. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + grandparent_1: + age: 70 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 4_000 + savings_interest_income: 300 + working_tax_credit_reported: 1 + grandparent_2: + age: 70 + is_claimant_or_partner: true + state_pension: 4_000 + grandchild: + age: 10 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent_1, grandparent_2, grandchild] + would_claim_uc: false + pension_credit_earnings: 2_080 + households: + household: + members: [grandparent_1, grandparent_2, grandchild] + savings: 10_000 + output: + has_tax_credit_award: true + child_minimum_guarantee_addition: 0 + tax_credits_applicable_income: 0 + working_tax_credit: 4_935 + child_tax_credit: 0 + pension_credit: 2_298.40 + +- name: An award at a nil rate is still an award + # Tax credit income: 9,000 + 8,000 + (300 - 300) + 3,500 = £20,500. WTC on + # the income test: 4,935 - 0.41 x (20,500 - 7,955) < 0, so nil. + # Pension Credit income: 17,000 + (3,500 - 520) = £19,980, above the + # £17,313.40 guarantee, so no Pension Credit, and with the passported WTC + # (£4,935) still none: the income test applies. + # The family has claimed WTC and meets its conditions, so it holds an award + # at a nil rate (TCA 2002 s.14(3)) and gets no Schedule IIA amount. Had the + # award followed a positive amount, the child amount would give Guarantee + # Credit of 21,306.48 - 19,980 = £1,326.48 with the income test still + # applied, though that Pension Credit would lift it. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + grandparent_1: + age: 70 + is_claimant_or_partner: true + employment_income: 3_500 + weekly_hours: 20 + state_pension: 9_000 + savings_interest_income: 300 + working_tax_credit_reported: 1 + grandparent_2: + age: 70 + is_claimant_or_partner: true + state_pension: 8_000 + grandchild: + age: 10 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent_1, grandparent_2, grandchild] + would_claim_uc: false + pension_credit_earnings: 2_980 # 3,500 - 10 x 52 + households: + household: + members: [grandparent_1, grandparent_2, grandchild] + savings: 10_000 + output: + has_tax_credit_award: true + tax_credits_applicable_income: 20_500 + working_tax_credit: 0 + child_minimum_guarantee_addition: 0 + pension_credit: 0 + +- name: With tax credits ended, a reported award no longer removes the child amount + # Tax credits ended on 5 April 2025, so there is no award in 2025-26. + # Schedule IIA: 78.10 x 52 = £4,061.20. Minimum guarantee: 346.60 x 52 + + # 4,061.20 = 18,023.20 + 4,061.20 = £22,084.40. Guarantee Credit: + # 22,084.40 - 10,080 = £12,004.40. The standard minimum guarantee is entered + # at its statutory amount: the model projects 2025-26 by CPI (#1925). + period: 2025 + absolute_error_margin: 0.01 + input: + people: + grandparent_1: + age: 71 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 4_000 + savings_interest_income: 300 + child_tax_credit_reported: 1 + working_tax_credit_reported: 1 + grandparent_2: + age: 71 + is_claimant_or_partner: true + state_pension: 4_000 + grandchild: + age: 11 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent_1, grandparent_2, grandchild] + would_claim_uc: false + pension_credit_earnings: 2_080 + standard_minimum_guarantee: 18_023.20 # 346.60 x 52 + households: + household: + members: [grandparent_1, grandparent_2, grandchild] + savings: 10_000 + output: + has_tax_credit_award: false + working_tax_credit: 0 + child_tax_credit: 0 + child_minimum_guarantee_addition: 4_061.20 + minimum_guarantee: 22_084.40 + guarantee_credit: 12_004.40 + +- name: In 2019-20 the child amount applies, except with a tax credit award + # Schedule IIA from 1 February 2019. 2019-20: 63.84 x 52 = £3,319.68; + # couple guarantee 255.25 x 52 = £13,273. The first couple has a CTC award, + # the second none. + period: 2019 + absolute_error_margin: 0.01 + input: + people: + awarded_1: + age: 70 + is_claimant_or_partner: true + child_tax_credit_reported: 1 + awarded_2: + age: 70 + is_claimant_or_partner: true + awarded_grandchild: + age: 10 + is_claimant_or_partner: false + not_awarded_1: + age: 70 + is_claimant_or_partner: true + not_awarded_2: + age: 70 + is_claimant_or_partner: true + not_awarded_grandchild: + age: 10 + is_claimant_or_partner: false + benunits: + awarded: + members: [awarded_1, awarded_2, awarded_grandchild] + would_claim_uc: false + not_awarded: + members: [not_awarded_1, not_awarded_2, not_awarded_grandchild] + would_claim_uc: false + households: + awarded: + members: [awarded_1, awarded_2, awarded_grandchild] + not_awarded: + members: [not_awarded_1, not_awarded_2, not_awarded_grandchild] + output: + has_tax_credit_award: [true, false] + child_minimum_guarantee_addition: [0, 3_319.68] + minimum_guarantee: [13_273, 16_592.68] + +- name: Before February 2019 there was no Schedule IIA amount + # 2018-19 starts on 6 April 2018, before SI 2018/676 added Schedule IIA. + # The couple guarantee is 248.80 x 52 = £12,937.60, with nothing for the + # grandchild, whom Child Tax Credit supported instead. + period: 2018 + absolute_error_margin: 0.01 + input: + people: + grandparent_1: + age: 70 + is_claimant_or_partner: true + grandparent_2: + age: 70 + is_claimant_or_partner: true + grandchild: + age: 10 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent_1, grandparent_2, grandchild] + would_claim_uc: false + households: + household: + members: [grandparent_1, grandparent_2, grandchild] + output: + has_tax_credit_award: false + child_minimum_guarantee_addition: 0 + minimum_guarantee: 12_937.60 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_pension_credit_passport.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_pension_credit_passport.yaml index a5e7a5fb78..27d2a38d4b 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_pension_credit_passport.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/tax_credits_pension_credit_passport.yaml @@ -204,8 +204,8 @@ # = £866.64, so £123.34. # CTC: £545 + £3,455 = £4,000, not 4,000 - 0.41 x 205 = £3,915.95. # SPC Regs 2002 reg 6(6)(d) and (11) give no Schedule IIA child amount to - # a person awarded a tax credit, and the CTC family element has been £545 - # since 2011-12; both are entered because the model applies neither. + # a person awarded a tax credit. The CTC family element has been £545 since + # 2011-12; it is entered because the model CPI-uprates it. period: 2024 absolute_error_margin: 0.01 input: @@ -229,7 +229,6 @@ members: [grandparent_1, grandparent_2, grandchild] would_claim_uc: false pension_credit_earnings: 2_480 # 3,000 - 10 x 52 - child_minimum_guarantee_addition: 0 CTC_family_element: 545 households: household: @@ -273,7 +272,6 @@ members: [grandparent_1, grandparent_2, grandchild] would_claim_uc: false pension_credit_earnings: 2_480 - child_minimum_guarantee_addition: 0 CTC_family_element: 545 working_tax_credit_run_on: false extended_childcare_entitlement_eligible: false diff --git a/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py b/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py new file mode 100644 index 0000000000..b8b9962858 --- /dev/null +++ b/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py @@ -0,0 +1,301 @@ +"""No Pension Credit child amount for a tax credit award. + +SPC Regs 2002 reg 6(6)(d) adds the Schedule IIA amount for each child or +qualifying young person "except where paragraph (11) applies": where the +person "is awarded, or ... is treated as having an award of, a tax credit" +(reg 6(11)), child tax credit or working tax credit (reg 6(17)). Reg 6(6)(d) +and Schedule IIA apply from 1 February 2019 (SI 2018/676), so from 2019-20 in +the model. Tax credits ended on 5 April 2025. HMRC may award a tax credit at a +nil rate (TCA 2002 s.14(3)), so the award follows the claim and the +entitlement conditions other than the income test. + +Properties, for any family: + +1. The award is a claim plus the conditions of entitlement, recomputed + independently, in every year with tax credits (2019-20 to 2024-25). +2. Differential: the child amount is nil where there is an award, and + otherwise equals the amount in a simulation with the award entered as + absent. That amount equals an independent Schedule IIA calculation from + the parameters (para 9(1)(a) and para 10). +3. After tax credits ended (2025-26 on), no family has an award, and the + child amount is the Schedule IIA amount. +4. Before 2019-20 the child amount is nil. +5. Removing the child amount never raises Pension Credit plus tax credits, + and changes nothing for a family with no award. +6. The results do not depend on the order in which the child amount, the + award, Pension Credit, the tax credit income test and household income + are requested, and no request raises a dependency cycle. +""" + +import numpy as np +from hypothesis import HealthCheck, example, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +TAX_CREDIT_YEARS = [2019, 2021, 2024] +NO_AWARD_YEARS = [2025, 2026] +BEFORE_SCHEDULE_IIA_YEARS = [2015, 2018] +SUPPORT = ["pension_credit", "working_tax_credit", "child_tax_credit"] +OUTPUTS = [ + "child_minimum_guarantee_addition", + "has_tax_credit_award", + "tax_credits_applicable_income", + "pension_credit", + "working_tax_credit", + "child_tax_credit", +] + + +@st.composite +def adults(draw, partner): + age = draw(st.integers(40 if partner else 60, 90)) + earnings = draw(st.sampled_from([0, 0, 2_600, 6_000, 12_000])) + return { + "age": age, + "employment_income": earnings, + "weekly_hours": draw(st.sampled_from([16, 20, 30])) if earnings else 0, + "state_pension": ( + draw(st.sampled_from([0, 4_000, 6_000, 9_000, 11_500])) if age >= 66 else 0 + ), + "private_pension_income": draw(st.sampled_from([0, 0, 1_500, 5_000])), + "savings_interest_income": draw(st.sampled_from([0, 0, 300, 500])), + "working_tax_credit_reported": draw(st.sampled_from([0, 1])), + "child_tax_credit_reported": draw(st.sampled_from([0, 1, 1])), + } + + +@st.composite +def families(draw): + members = [draw(adults(partner=False))] + if draw(st.booleans()): + members.append(draw(adults(partner=True))) + children = [ + {"age": draw(st.integers(0, 15))} for _ in range(draw(st.integers(0, 3))) + ] + benunit = { + "would_claim_pc": draw(st.sampled_from([True, True, True, False])), + # A Universal Credit claim leaves a reported CTC award ineligible. + "would_claim_uc": draw(st.sampled_from([False, False, False, True])), + } + household = {"savings": draw(st.sampled_from([0, 5_000, 12_000, 20_000]))} + return members, children, benunit, household + + +def situation(families, year, benunit_inputs=None): + people, benunits, households = {}, {}, {} + for i, (members, children, benunit, household) in enumerate(families): + names = [] + for j, inputs in enumerate(members): + name = f"a{i}_{j}" + people[name] = {k: {year: v} for k, v in inputs.items()} + people[name]["is_claimant_or_partner"] = {year: True} + names.append(name) + for j, inputs in enumerate(children): + name = f"c{i}_{j}" + people[name] = {k: {year: v} for k, v in inputs.items()} + people[name]["is_claimant_or_partner"] = {year: False} + names.append(name) + benunits[f"b{i}"] = { + "members": names, + **{k: {year: v} for k, v in benunit.items()}, + **{k: {year: v} for k, v in (benunit_inputs or {}).items()}, + } + households[f"h{i}"] = { + "members": names, + **{k: {year: v} for k, v in household.items()}, + } + return {"people": people, "benunits": benunits, "households": households} + + +def calculate(drawn, year, order=OUTPUTS, benunit_inputs=None): + sim = Simulation(situation=situation(drawn, year, benunit_inputs)) + return {variable: sim.calculate(variable, year) for variable in order} + + +def schedule_iia_amount(drawn, year): + """Schedule IIA amounts from the parameters, for children who are not + disabled: para 9(1)(a) for each child, and para 10's higher amount for + the eldest if born before 6 April 2017 (by birth year, as the model).""" + from policyengine_uk import CountryTaxBenefitSystem + + child = ( + CountryTaxBenefitSystem() + .parameters(year) + .gov.dwp.pension_credit.guarantee_credit.child + ) + amounts = [] + for _, children, _, _ in drawn: + ages = [c["age"] for c in children] + if not ages: + amounts.append(0) + continue + eldest_first = year - max(ages) < 2017 + weekly = len(ages) * child.addition + if eldest_first: + weekly += child.first.addition - child.addition + amounts.append(weekly * 52) + return np.array(amounts) + + +SETTINGS = settings( + max_examples=20, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +FAMILIES = st.lists(families(), min_size=1, max_size=6) + +# The YAML households (child_amount_tax_credit_award.yaml), run on every +# invocation. Pension Credit earnings are net of SPC Regs Sch VI para 5, and +# the CTC family element is entered at its statutory £545. +_GRANDPARENT = { + "age": 70, + "employment_income": 2_600, + "weekly_hours": 20, + "state_pension": 4_000, + "savings_interest_income": 300, +} +_INPUTS = {"would_claim_uc": False, "pension_credit_earnings": 2_080} +YAML_FAMILIES = [ + # CTC and WTC with Pension Credit: passported, no child amount. + ( + [ + { + **_GRANDPARENT, + "child_tax_credit_reported": 1, + "working_tax_credit_reported": 1, + }, + {"age": 70, "state_pension": 4_000}, + ], + [{"age": 10}], + {**_INPUTS, "CTC_family_element": 545}, + {"savings": 10_000}, + ), + # No tax credit claim: the child amount. + ( + [_GRANDPARENT, {"age": 70, "state_pension": 4_000}], + [{"age": 10}], + _INPUTS, + {"savings": 10_000}, + ), + # WTC alone. + ( + [ + {**_GRANDPARENT, "working_tax_credit_reported": 1}, + {"age": 70, "state_pension": 4_000}, + ], + [{"age": 10}], + _INPUTS, + {"savings": 10_000}, + ), + # A WTC award at a nil rate. + ( + [ + { + **_GRANDPARENT, + "employment_income": 3_500, + "state_pension": 9_000, + "working_tax_credit_reported": 1, + }, + {"age": 70, "state_pension": 8_000}, + ], + [{"age": 10}], + {**_INPUTS, "pension_credit_earnings": 2_980}, + {"savings": 10_000}, + ), +] + + +@SETTINGS +@given(FAMILIES, st.sampled_from(TAX_CREDIT_YEARS)) +@example(YAML_FAMILIES, 2024) +def test_award_removes_the_child_amount_and_nothing_else(drawn, year): + conditions = [ + "would_claim_CTC", + "is_CTC_eligible", + "would_claim_WTC", + "is_WTC_eligible", + ] + on = calculate(drawn, year, order=OUTPUTS + conditions) + # 1. The award is a claim plus the conditions of entitlement. + expected_award = (on["would_claim_CTC"] & on["is_CTC_eligible"]) | ( + on["would_claim_WTC"] & on["is_WTC_eligible"] + ) + assert (on["has_tax_credit_award"] == expected_award).all(), drawn + # 2. Differential, and the Schedule IIA amount from the parameters. + without = calculate(drawn, year, benunit_inputs={"has_tax_credit_award": False}) + expected = np.where( + on["has_tax_credit_award"], 0, without["child_minimum_guarantee_addition"] + ) + assert np.allclose(on["child_minimum_guarantee_addition"], expected), drawn + assert np.allclose( + without["child_minimum_guarantee_addition"], + schedule_iia_amount(drawn, year), + atol=0.01, + ), drawn + # 5. Removing the child amount never raises Pension Credit plus tax + # credits, and changes nothing without an award. + total_on = sum(on[v] for v in SUPPORT) + total_without = sum(without[v] for v in SUPPORT) + assert (total_on <= total_without + 0.01).all(), drawn + no_award = ~on["has_tax_credit_award"] + for variable in SUPPORT + ["tax_credits_applicable_income"]: + assert np.allclose(on[variable][no_award], without[variable][no_award]), ( + variable, + drawn, + ) + + +@SETTINGS +@given(FAMILIES, st.sampled_from(NO_AWARD_YEARS)) +def test_no_award_after_tax_credits_ended(drawn, year): + on = calculate(drawn, year) + assert not on["has_tax_credit_award"].any() + assert np.allclose( + on["child_minimum_guarantee_addition"], + schedule_iia_amount(drawn, year), + atol=0.01, + ), drawn + + +@SETTINGS +@given(FAMILIES, st.sampled_from(BEFORE_SCHEDULE_IIA_YEARS)) +def test_no_child_amount_before_schedule_iia(drawn, year): + on = calculate(drawn, year) + assert (on["child_minimum_guarantee_addition"] == 0).all(), drawn + + +@SETTINGS +@given(FAMILIES, st.sampled_from(TAX_CREDIT_YEARS + NO_AWARD_YEARS)) +@example(YAML_FAMILIES, 2024) +def test_results_do_not_depend_on_request_order(drawn, year): + forward = calculate(drawn, year) + backward = calculate(drawn, year, order=list(reversed(OUTPUTS))) + household_first = calculate(drawn, year, order=["household_net_income"] + OUTPUTS) + for variable in OUTPUTS: + assert np.allclose(forward[variable], backward[variable]), (variable, drawn) + assert np.allclose(forward[variable], household_first[variable]), ( + variable, + drawn, + ) + + +def test_yaml_families_reach_every_case(): + """The pinned examples are not vacuous: they hit each case of the rule.""" + year = 2024 + on = calculate(YAML_FAMILIES, year) + without = calculate( + YAML_FAMILIES, year, benunit_inputs={"has_tax_credit_award": False} + ) + assert on["has_tax_credit_award"].tolist() == [True, False, True, True] + # The rule removes the child amount from each awarded family. + assert (without["child_minimum_guarantee_addition"] > 0).all() + assert np.allclose(on["child_minimum_guarantee_addition"], [0, 3_993.08, 0, 0]) + # Pension Credit, CTC and WTC are all paid to the first family. + for variable in SUPPORT: + assert on[variable][0] > 0, variable + # The nil-rate award: no WTC, no Pension Credit, no child amount. + assert on["working_tax_credit"][3] == 0 + assert on["pension_credit"][3] == 0 + assert without["pension_credit"][3] > 0 diff --git a/policyengine_uk/tests/test_tax_credits_pension_credit_passport_properties.py b/policyengine_uk/tests/test_tax_credits_pension_credit_passport_properties.py index fa6d49c562..34d607b36f 100644 --- a/policyengine_uk/tests/test_tax_credits_pension_credit_passport_properties.py +++ b/policyengine_uk/tests/test_tax_credits_pension_credit_passport_properties.py @@ -142,8 +142,8 @@ def calculate(sim, year, order=OUTPUTS): # The YAML cases, run on every invocation: random draws seldom raise CTC or # hit the third row. Pension Credit earnings are net of SPC Regs Sch VI -# para 5; the CTC family element and the Sch IIA child amount are entered at -# their statutory values (see the YAML file). +# para 5; the CTC family element is entered at its statutory value (see the +# YAML file). _PENSIONER = { "age": 70, "employment_income": 2_600, @@ -181,7 +181,6 @@ def calculate(sim, year, order=OUTPUTS): [{"age": 10}], { "pension_credit_earnings": 2_480, - "child_minimum_guarantee_addition": 0, "CTC_family_element": 545, }, {"savings": 10_000}, diff --git a/policyengine_uk/variables/gov/dwp/has_tax_credit_award.py b/policyengine_uk/variables/gov/dwp/has_tax_credit_award.py new file mode 100644 index 0000000000..b4bd69d42f --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/has_tax_credit_award.py @@ -0,0 +1,35 @@ +from policyengine_uk.model_api import * + + +class has_tax_credit_award(Variable): + value_type = bool + entity = BenUnit + label = "has a tax credit award" + documentation = ( + "Whether the family is awarded child tax credit or working tax credit " + "for the year, at any rate. Entitlement depends on a claim (TCA 2002 " + "s.3(1)), which a couple makes jointly (s.3(3)(a)), so an award is " + "both members'. On a claim HMRC decide whether to make an award and " + "at what rate (s.14(1)), and may award a tax credit at a nil rate " + "(s.14(3)). So the award follows the claim and the conditions of " + "entitlement other than the income test, not a positive amount. Tax " + "credits ended on 5 April 2025, so there are no awards after that." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/ukpga/2002/21/section/3", + "https://www.legislation.gov.uk/ukpga/2002/21/section/14", + ) + + def formula(benunit, period, parameters): + if not parameters(period).gov.dwp.tax_credits.active: + return benunit.filled_array(False) + # Neither condition reads Pension Credit, which reads this award + # (child_minimum_guarantee_addition), so no cycle arises. + child_tax_credit = benunit("would_claim_CTC", period) & benunit( + "is_CTC_eligible", period + ) + working_tax_credit = benunit("would_claim_WTC", period) & benunit( + "is_WTC_eligible", period + ) + return child_tax_credit | working_tax_credit diff --git a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/child_minimum_guarantee_addition.py b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/child_minimum_guarantee_addition.py index 8a6677bece..7bc48ae7ec 100644 --- a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/child_minimum_guarantee_addition.py +++ b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/child_minimum_guarantee_addition.py @@ -7,9 +7,17 @@ class child_minimum_guarantee_addition(Variable): definition_period = YEAR value_type = float unit = GBP - reference = "https://www.legislation.gov.uk/uksi/2002/1792/schedule/IIA" + reference = ( + "https://www.legislation.gov.uk/uksi/2002/1792/regulation/6", + "https://www.legislation.gov.uk/uksi/2002/1792/schedule/IIA", + ) def formula(benunit, period, parameters): + gc = parameters(period).gov.dwp.pension_credit.guarantee_credit + # SPC Regs 2002 reg 6(6)(d) and Schedule IIA apply from 1 February + # 2019 (SI 2018/676 reg 2). + if not gc.child.in_effect: + return benunit.empty_array() person = benunit.members is_child_or_qualifying_young_person = person( "is_child_or_qualifying_young_person_for_pension_credit", period @@ -25,7 +33,6 @@ def formula(benunit, period, parameters): first_child_born_before_2017 = (child_index == 1) & ( person("birth_year", period) < 2017 ) - gc = parameters(period).gov.dwp.pension_credit.guarantee_credit standard_disability_benefits = gc.child.disability.eligibility severe_disability_benefits = gc.child.disability.severe.eligibility is_disabled = add(person, period, standard_disability_benefits) > 0 @@ -52,4 +59,14 @@ def formula(benunit, period, parameters): ) * WEEKS_IN_YEAR ) - return benunit.sum(per_child_amount) + # Reg 6(6)(d) applies Schedule IIA "except where paragraph (11) + # applies", which is "the case of a person who is awarded, or who is + # treated as having an award of, a tax credit" (reg 6(11)): child tax + # credit or working tax credit (reg 6(17)). The tax credit award then + # carries the support for the child. Reg 6(12) and (13) treat an + # award as continuing from the start of a tax year until it is + # renewed or finalised, and reg 6(14) to (16) end the amount when an + # award is made late; on the model's whole-year awards, both affect + # only part of a year, and are not modelled. + has_tax_credit_award = benunit("has_tax_credit_award", period) + return benunit.sum(per_child_amount) * ~has_tax_credit_award From c96c4101e0d7e6bd7693a409acaf8b4801712fbd Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Tue, 13 Oct 2026 16:10:25 -0400 Subject: [PATCH 2/5] Answer review r1b: award proxy, renewal wording, HB and savings credit cases - has_tax_credit_award documents that it infers the award from reported amounts, so a nil or deemed award reported as zero must be entered. - child_minimum_guarantee_addition: a WTC award alone need not include anything for the child; renewal, finalisation and revision history are unmodelled inputs, not part-year effects. - YAML: an award entered directly; a couple aged 80 whose CTC award removes the child amount, leaving savings credit but ending the Guarantee Credit passport to Housing Benefit, and its no-claim twin. The 2025-26 case enters that the couple does not claim Universal Credit at the tax credit closure (SI 2014/1230 reg 60A(1)(a)(iii)). - Property tests: say the award check is wiring, the Schedule IIA calculation shares parameters and dates of birth, and pin the two rented households so the order checks reach Housing Benefit. Co-Authored-By: Claude Opus 5.5 --- .../child_amount_tax_credit_award.yaml | 179 +++++++++++++++++- ...edit_child_amount_tax_credit_properties.py | 77 ++++++-- .../variables/gov/dwp/has_tax_credit_award.py | 9 +- .../child_minimum_guarantee_addition.py | 14 +- 4 files changed, 248 insertions(+), 31 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml index 5c1ecc1457..596e76f2b7 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml @@ -21,16 +21,27 @@ # - 2019-20: £255.25 couple; £63.84 eldest child. 2018-19: £248.80 couple. # - WTC 2024-25: basic element £2,435, couple element £2,500; threshold # £7,955; withdrawal rate 41%. CTC child element £3,455; family element £545 -# (entered: the model CPI-uprates it past its frozen £545). +# (entered when the model uprated it; since #1925 the model's own value is +# the same). +# - Savings credit 2024-25: threshold £301.22 a week for a couple, so the +# maximum is 0.6 x (332.95 - 301.22) x 52 = £989.98 (SPCA 2002 s.3(7)). +# - Capital: Pension Credit and pension-age Housing Benefit count £1 a week +# for each £500 over £10,000 (SPC Regs 2002 reg 15(6); SI 2006/214 +# reg 29(2)); pension-age Housing Benefit's capital limit is £16,000 (SI +# 2006/214 reg 43). # WEEKS_IN_YEAR is 52. # -# Each couple is aged 70, so both reached pensionable age after 6 April 2016 -# and there is no savings credit (SPCA 2002 s.3(1)(a)). The grandchild is 10, -# born before 6 April 2017. Pension Credit earnings are entered net of the SPC -# Regs Sch VI para 5 disregard (£10 a week for a couple), which the model does -# not apply. £10,000 of savings gives no Pension Credit tariff income, and -# £300 of interest uses up the £300 step-one disregard of tax credit income -# (SI 2002/2006 reg 3). No one pays income tax or National Insurance. +# The couples in the 2024-25 and 2025-26 cases are aged 70 in 2024-25, so +# both reached pensionable age after 6 April 2016 and there is no savings +# credit (SPCA 2002 s.3(1)(a)). The 2018-19 and 2019-20 couples have no +# income, so no savings credit either. The Housing Benefit cases' couple is +# aged 80, so savings credit applies to it. The grandchild is 10 (11 in +# 2025-26), born before 6 April 2017. Pension Credit earnings are entered net +# of the SPC Regs Sch VI para 5 disregard (£10 a week for a couple), which +# the model does not apply. £10,000 of savings gives no Pension Credit tariff +# income, and £300 of interest uses up the £300 step-one disregard of tax +# credit income (SI 2002/2006 reg 3). No one pays income tax or National +# Insurance. - name: A Pension Credit couple awarded CTC and WTC gets no child amount # Tax credit income: State Pension 8,000 + interest (300 - 300) + earnings @@ -211,12 +222,159 @@ child_minimum_guarantee_addition: 0 pension_credit: 0 +- name: An award entered directly removes the child amount + # The award follows reported tax credits, so an award the reported amounts + # do not show (one reported as nil, or one treated as continuing under reg + # 6(12) and (13)) is entered as has_tax_credit_award. The second case's + # couple with an award entered: no Schedule IIA amount, so the minimum + # guarantee is £17,313.40 and Guarantee Credit 17,313.40 - 10,080 = + # £7,233.40. No tax credit is claimed, so none is paid. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + grandparent_1: + age: 70 + is_claimant_or_partner: true + employment_income: 2_600 + weekly_hours: 20 + state_pension: 4_000 + savings_interest_income: 300 + grandparent_2: + age: 70 + is_claimant_or_partner: true + state_pension: 4_000 + grandchild: + age: 10 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent_1, grandparent_2, grandchild] + would_claim_uc: false + pension_credit_earnings: 2_080 + has_tax_credit_award: true + households: + household: + members: [grandparent_1, grandparent_2, grandchild] + savings: 10_000 + output: + child_minimum_guarantee_addition: 0 + minimum_guarantee: 17_313.40 + working_tax_credit: 0 + child_tax_credit: 0 + guarantee_credit: 7_233.40 + pension_credit: 7_233.40 + +- name: Without the child amount, a CTC award can end the Housing Benefit passport + # A couple aged 80 with a grandchild, claiming CTC, with State Pension of + # £9,000 each, £18,000 of savings and £12,000 a year of council rent. + # Pension Credit income: 18,000 + tariff income (18,000 - 10,000) / 500 = + # £16 a week, 16 x 52 = £832, so £18,832. + # With the CTC award there is no Schedule IIA amount: the minimum guarantee + # is £17,313.40, below income, so no Guarantee Credit. Savings credit + # (SPCA 2002 s.3(2)-(4)): A is the maximum, £989.98 (60% of qualifying + # income over the threshold, 0.6 x (18,832 - 301.22 x 52) = £1,901.14, is + # more); B = 0.4 x (18,832 - 17,313.40) = £607.44; so 989.98 - 607.44 = + # £382.54. CTC: 545 + 3,455 = £4,000, the income test lifted by the savings + # credit (SI 2002/2008 reg 4(1)(d)). + # Housing Benefit: with no Guarantee Credit there is no passport (SI + # 2006/214 reg 26), and capital of £18,000 is over the £16,000 limit, so + # none. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + grandparent_1: + age: 80 + is_claimant_or_partner: true + state_pension: 9_000 + child_tax_credit_reported: 1 + grandparent_2: + age: 80 + is_claimant_or_partner: true + state_pension: 9_000 + grandchild: + age: 10 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent_1, grandparent_2, grandchild] + would_claim_uc: false + households: + household: + members: [grandparent_1, grandparent_2, grandchild] + savings: 18_000 + rent: 12_000 + tenure_type: RENT_FROM_COUNCIL + output: + has_tax_credit_award: true + child_minimum_guarantee_addition: 0 + minimum_guarantee: 17_313.40 + pension_credit_income: 18_832 + guarantee_credit: 0 + savings_credit: 382.54 + pension_credit: 382.54 + child_tax_credit: 4_000 + housing_benefit_assessable_capital: 18_000 + housing_benefit: 0 + +- name: The same household with no tax credit claim keeps the passport + # Schedule IIA para 10: 76.79 x 52 = £3,993.08, so the minimum guarantee is + # 17,313.40 + 3,993.08 = £21,306.48 and Guarantee Credit 21,306.48 - 18,832 + # = £2,474.48. Savings credit: income is below the minimum guarantee, so B + # is nil and the credit is the maximum, £989.98. Pension Credit £3,464.46. + # Housing Benefit: the Guarantee Credit passport disregards all capital and + # income (SI 2006/214 reg 26), so the full eligible rent, £12,000. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + grandparent_1: + age: 80 + is_claimant_or_partner: true + state_pension: 9_000 + grandparent_2: + age: 80 + is_claimant_or_partner: true + state_pension: 9_000 + grandchild: + age: 10 + is_claimant_or_partner: false + benunits: + benunit: + members: [grandparent_1, grandparent_2, grandchild] + would_claim_uc: false + households: + household: + members: [grandparent_1, grandparent_2, grandchild] + savings: 18_000 + rent: 12_000 + tenure_type: RENT_FROM_COUNCIL + output: + has_tax_credit_award: false + child_minimum_guarantee_addition: 3_993.08 + minimum_guarantee: 21_306.48 + guarantee_credit: 2_474.48 + savings_credit: 989.98 + pension_credit: 3_464.46 + child_tax_credit: 0 + housing_benefit_assessable_capital: 0 + housing_benefit: 12_000 + - name: With tax credits ended, a reported award no longer removes the child amount # Tax credits ended on 5 April 2025, so there is no award in 2025-26. # Schedule IIA: 78.10 x 52 = £4,061.20. Minimum guarantee: 346.60 x 52 + # 4,061.20 = 18,023.20 + 4,061.20 = £22,084.40. Guarantee Credit: - # 22,084.40 - 10,080 = £12,004.40. The standard minimum guarantee is entered - # at its statutory amount: the model projects 2025-26 by CPI (#1925). + # 22,084.40 - 10,080 = £12,004.40. The standard minimum guarantee is + # entered at its statutory amount: it was entered when the model projected + # 2025-26 by CPI, and since #1925 the model's own value is the same. + # The couple cannot claim Universal Credit when its tax credits close: both + # are over the qualifying age for Pension Credit (WRA 2012 s.4(1)(b)), and + # SI 2014/1230 reg 60A waives that only for a Working Tax Credit claimant + # who "is not entitled to an award of state pension credit" + # (reg 60A(1)(a)(iii)), which this couple is (the first case). The model's + # closure routing does not test that condition, so the case enters that the + # couple does not claim Universal Credit then. period: 2025 absolute_error_margin: 0.01 input: @@ -243,6 +401,7 @@ would_claim_uc: false pension_credit_earnings: 2_080 standard_minimum_guarantee: 18_023.20 # 346.60 x 52 + would_claim_uc_at_legacy_closure: false households: household: members: [grandparent_1, grandparent_2, grandchild] diff --git a/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py b/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py index b8b9962858..40118b91fb 100644 --- a/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py +++ b/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py @@ -11,20 +11,27 @@ Properties, for any family: -1. The award is a claim plus the conditions of entitlement, recomputed - independently, in every year with tax credits (2019-20 to 2024-25). +1. The award is the claim and the conditions of entitlement, read from + would_claim_CTC, is_CTC_eligible, would_claim_WTC and is_WTC_eligible, in + sampled years with tax credits (2019-20, 2021-22 and 2024-25). This checks + the wiring: it cannot find an error in those variables, which infer a + claim from a positive reported award. 2. Differential: the child amount is nil where there is an award, and otherwise equals the amount in a simulation with the award entered as - absent. That amount equals an independent Schedule IIA calculation from - the parameters (para 9(1)(a) and para 10). + absent. That amount equals a separate Schedule IIA calculation (para + 9(1)(a) and para 10). The calculation reads the same rate parameters and + the model's dates of birth, so it checks the formula, not the rates or + the exact 6 April 2017 boundary. 3. After tax credits ended (2025-26 on), no family has an award, and the child amount is the Schedule IIA amount. 4. Before 2019-20 the child amount is nil. 5. Removing the child amount never raises Pension Credit plus tax credits, - and changes nothing for a family with no award. + and changes nothing for a family with no award. Other support can fall: + losing Guarantee Credit ends the Housing Benefit passport (a pinned + family below and the YAML cases). 6. The results do not depend on the order in which the child amount, the - award, Pension Credit, the tax credit income test and household income - are requested, and no request raises a dependency cycle. + award, Pension Credit, the tax credit income test, Housing Benefit and + household income are requested, and no request raises a dependency cycle. """ import numpy as np @@ -44,6 +51,7 @@ "pension_credit", "working_tax_credit", "child_tax_credit", + "housing_benefit", ] @@ -116,7 +124,10 @@ def calculate(drawn, year, order=OUTPUTS, benunit_inputs=None): def schedule_iia_amount(drawn, year): """Schedule IIA amounts from the parameters, for children who are not disabled: para 9(1)(a) for each child, and para 10's higher amount for - the eldest if born before 6 April 2017 (by birth year, as the model).""" + the eldest if born before 6 April 2017. With the default six months + since the last birthday, the model puts the birth of a child aged a in + year y on 6 April of y - a, so before 6 April 2017 exactly when + y - a < 2017.""" from policyengine_uk import CountryTaxBenefitSystem child = ( @@ -148,7 +159,8 @@ def schedule_iia_amount(drawn, year): # The YAML households (child_amount_tax_credit_award.yaml), run on every # invocation. Pension Credit earnings are net of SPC Regs Sch VI para 5, and -# the CTC family element is entered at its statutory £545. +# the CTC family element is entered at its statutory £545. The last two pay +# rent, so the order and differential checks reach Housing Benefit. _GRANDPARENT = { "age": 70, "employment_income": 2_600, @@ -157,6 +169,7 @@ def schedule_iia_amount(drawn, year): "savings_interest_income": 300, } _INPUTS = {"would_claim_uc": False, "pension_credit_earnings": 2_080} +_RENTED = {"savings": 18_000, "rent": 12_000, "tenure_type": "RENT_FROM_COUNCIL"} YAML_FAMILIES = [ # CTC and WTC with Pension Credit: passported, no child amount. ( @@ -204,6 +217,23 @@ def schedule_iia_amount(drawn, year): {**_INPUTS, "pension_credit_earnings": 2_980}, {"savings": 10_000}, ), + # A CTC award ends the Guarantee Credit and its Housing Benefit passport. + ( + [ + {"age": 80, "state_pension": 9_000, "child_tax_credit_reported": 1}, + {"age": 80, "state_pension": 9_000}, + ], + [{"age": 10}], + {"would_claim_uc": False}, + _RENTED, + ), + # The same household with no tax credit claim keeps the passport. + ( + [{"age": 80, "state_pension": 9_000}, {"age": 80, "state_pension": 9_000}], + [{"age": 10}], + {"would_claim_uc": False}, + _RENTED, + ), ] @@ -284,14 +314,28 @@ def test_results_do_not_depend_on_request_order(drawn, year): def test_yaml_families_reach_every_case(): """The pinned examples are not vacuous: they hit each case of the rule.""" year = 2024 - on = calculate(YAML_FAMILIES, year) + order = OUTPUTS + ["guarantee_credit", "savings_credit"] + on = calculate(YAML_FAMILIES, year, order=order) without = calculate( - YAML_FAMILIES, year, benunit_inputs={"has_tax_credit_award": False} + YAML_FAMILIES, + year, + order=order, + benunit_inputs={"has_tax_credit_award": False}, ) - assert on["has_tax_credit_award"].tolist() == [True, False, True, True] + assert on["has_tax_credit_award"].tolist() == [ + True, + False, + True, + True, + True, + False, + ] # The rule removes the child amount from each awarded family. assert (without["child_minimum_guarantee_addition"] > 0).all() - assert np.allclose(on["child_minimum_guarantee_addition"], [0, 3_993.08, 0, 0]) + assert np.allclose( + on["child_minimum_guarantee_addition"], + [0, 3_993.08, 0, 0, 0, 3_993.08], + ) # Pension Credit, CTC and WTC are all paid to the first family. for variable in SUPPORT: assert on[variable][0] > 0, variable @@ -299,3 +343,10 @@ def test_yaml_families_reach_every_case(): assert on["working_tax_credit"][3] == 0 assert on["pension_credit"][3] == 0 assert without["pension_credit"][3] > 0 + # The fifth family: the award leaves savings credit but no Guarantee + # Credit, so no Housing Benefit passport; the sixth keeps both. + assert on["guarantee_credit"][4] == 0 < without["guarantee_credit"][4] + assert 0 < on["savings_credit"][4] < without["savings_credit"][4] + assert on["housing_benefit"][4] == 0 < without["housing_benefit"][4] + assert on["guarantee_credit"][5] > 0 + assert on["housing_benefit"][5] > 0 diff --git a/policyengine_uk/variables/gov/dwp/has_tax_credit_award.py b/policyengine_uk/variables/gov/dwp/has_tax_credit_award.py index b4bd69d42f..911e11f21c 100644 --- a/policyengine_uk/variables/gov/dwp/has_tax_credit_award.py +++ b/policyengine_uk/variables/gov/dwp/has_tax_credit_award.py @@ -12,8 +12,13 @@ class has_tax_credit_award(Variable): "both members'. On a claim HMRC decide whether to make an award and " "at what rate (s.14(1)), and may award a tax credit at a nil rate " "(s.14(3)). So the award follows the claim and the conditions of " - "entitlement other than the income test, not a positive amount. Tax " - "credits ended on 5 April 2025, so there are no awards after that." + "entitlement other than the income test, not a positive amount. The " + "model reads the claim and those conditions from reported awards: " + "is_CTC_eligible and is_WTC_eligible need a positive reported " + "amount, so an award reported as nil, or one treated as continuing " + "during renewal (SPC Regs 2002 reg 6(12)), is not inferred; enter " + "this variable for one. Tax credits ended on 5 April 2025, so there " + "are no awards after that." ) definition_period = YEAR reference = ( diff --git a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/child_minimum_guarantee_addition.py b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/child_minimum_guarantee_addition.py index 07cc19bd06..9cc98e916a 100644 --- a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/child_minimum_guarantee_addition.py +++ b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/child_minimum_guarantee_addition.py @@ -66,11 +66,13 @@ def formula(benunit, period, parameters): # Reg 6(6)(d) applies Schedule IIA "except where paragraph (11) # applies", which is "the case of a person who is awarded, or who is # treated as having an award of, a tax credit" (reg 6(11)): child tax - # credit or working tax credit (reg 6(17)). The tax credit award then - # carries the support for the child. Reg 6(12) and (13) treat an - # award as continuing from the start of a tax year until it is - # renewed or finalised, and reg 6(14) to (16) end the amount when an - # award is made late; on the model's whole-year awards, both affect - # only part of a year, and are not modelled. + # credit or working tax credit (reg 6(17)), so a working tax credit + # award alone, which need not include anything for the child, also + # removes the amount. Reg 6(12) and (13) treat last year's award as + # continuing from the start of a tax year until it is renewed or + # finalised, and reg 6(14) to (16) end the amount when an award is + # made late. The model has no renewal, finalisation or revision + # history, so neither is modelled; a deemed award can be entered as + # has_tax_credit_award. has_tax_credit_award = benunit("has_tax_credit_award", period) return benunit.sum(per_child_amount) * ~has_tax_credit_award From 5c0dc4a146e6828199a6f6928f9f6edde909bd30 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 14 Oct 2026 21:27:26 -0400 Subject: [PATCH 3/5] Enter the Housing Benefit claim in the rented test households MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Review r2 found the coverage test failing at line 350: the model infers Housing Benefit take-up from reported benefits across the simulation (claims_all_entitled_benefits), so a reported tax credit left Housing Benefit unclaimed, and nil whatever the Guarantee Credit passport. Both rented YAML cases and both pinned property families now enter would_claim_housing_benefit, so the passport, not take-up, decides Housing Benefit. The savings credit note says the model does not round the weekly maximum (DWP publishes £19.04 a week). Co-Authored-By: Claude Opus 5.5 --- .../child_amount_tax_credit_award.yaml | 12 ++++++++++-- ...sion_credit_child_amount_tax_credit_properties.py | 8 +++++--- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml index 596e76f2b7..2b8e58ff0e 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/pension_credit/guarantee_credit/child_amount_tax_credit_award.yaml @@ -25,6 +25,9 @@ # the same). # - Savings credit 2024-25: threshold £301.22 a week for a couple, so the # maximum is 0.6 x (332.95 - 301.22) x 52 = £989.98 (SPCA 2002 s.3(7)). +# The model works in annual amounts and does not round the weekly +# maximum; DWP publishes it rounded, £19.04 a week (£990.08 over 52 +# weeks). # - Capital: Pension Credit and pension-age Housing Benefit count £1 a week # for each £500 over £10,000 (SPC Regs 2002 reg 15(6); SI 2006/214 # reg 29(2)); pension-age Housing Benefit's capital limit is £16,000 (SI @@ -279,7 +282,9 @@ # credit (SI 2002/2008 reg 4(1)(d)). # Housing Benefit: with no Guarantee Credit there is no passport (SI # 2006/214 reg 26), and capital of £18,000 is over the £16,000 limit, so - # none. + # none. The case enters the Housing Benefit claim: the model infers + # take-up from reported benefits, so a reported tax credit alone would + # leave Housing Benefit unclaimed and nil whatever the passport. period: 2024 absolute_error_margin: 0.01 input: @@ -300,6 +305,7 @@ benunit: members: [grandparent_1, grandparent_2, grandchild] would_claim_uc: false + would_claim_housing_benefit: true households: household: members: [grandparent_1, grandparent_2, grandchild] @@ -324,7 +330,8 @@ # = £2,474.48. Savings credit: income is below the minimum guarantee, so B # is nil and the credit is the maximum, £989.98. Pension Credit £3,464.46. # Housing Benefit: the Guarantee Credit passport disregards all capital and - # income (SI 2006/214 reg 26), so the full eligible rent, £12,000. + # income (SI 2006/214 reg 26), so the full eligible rent, £12,000. The + # Housing Benefit claim is entered as in the previous case. period: 2024 absolute_error_margin: 0.01 input: @@ -344,6 +351,7 @@ benunit: members: [grandparent_1, grandparent_2, grandchild] would_claim_uc: false + would_claim_housing_benefit: true households: household: members: [grandparent_1, grandparent_2, grandchild] diff --git a/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py b/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py index 40118b91fb..87929cd78a 100644 --- a/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py +++ b/policyengine_uk/tests/test_pension_credit_child_amount_tax_credit_properties.py @@ -160,7 +160,9 @@ def schedule_iia_amount(drawn, year): # The YAML households (child_amount_tax_credit_award.yaml), run on every # invocation. Pension Credit earnings are net of SPC Regs Sch VI para 5, and # the CTC family element is entered at its statutory £545. The last two pay -# rent, so the order and differential checks reach Housing Benefit. +# rent, so the order and differential checks reach Housing Benefit; they +# enter the Housing Benefit claim, which the model otherwise infers from +# reported benefits across the whole simulation. _GRANDPARENT = { "age": 70, "employment_income": 2_600, @@ -224,14 +226,14 @@ def schedule_iia_amount(drawn, year): {"age": 80, "state_pension": 9_000}, ], [{"age": 10}], - {"would_claim_uc": False}, + {"would_claim_uc": False, "would_claim_housing_benefit": True}, _RENTED, ), # The same household with no tax credit claim keeps the passport. ( [{"age": 80, "state_pension": 9_000}, {"age": 80, "state_pension": 9_000}], [{"age": 10}], - {"would_claim_uc": False}, + {"would_claim_uc": False, "would_claim_housing_benefit": True}, _RENTED, ), ] From 6cae403f2922db7ee3349939459ee819613ae529 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 16 Oct 2026 16:25:52 -0400 Subject: [PATCH 4/5] Enter no tax credit award in the claimant-not-own-child property tests test_claimant_not_own_child_properties.py (from #2285, on main) supplies is_CTC_eligible to every unit so the CTC disabled child elements are paid. With that eligibility and the claim the model presumes, each unit has a tax credit award in 2024, and SPC Regs 2002 reg 6(6)(d) and (11) then give no Schedule IIA child addition, so CI failed test_disability_amounts_count_disabled_dependants_and_never_claimants on child_minimum_guarantee_addition (0 against the Schedule IIA amount). The invariants are about who counts as a dependant, not the award, so the helper now enters has_tax_credit_award as false, and the Pension Credit addition is paid and tested in every year, as before this PR. Co-Authored-By: Claude Opus 5.5 --- .../tests/test_claimant_not_own_child_properties.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/policyengine_uk/tests/test_claimant_not_own_child_properties.py b/policyengine_uk/tests/test_claimant_not_own_child_properties.py index 6ebe6e2e94..72933793b3 100644 --- a/policyengine_uk/tests/test_claimant_not_own_child_properties.py +++ b/policyengine_uk/tests/test_claimant_not_own_child_properties.py @@ -39,7 +39,11 @@ test_child_and_adult_definitions_properties.py covers. Tax credit eligibility is supplied too (is_CTC_eligible): without it both CTC disabled child elements are zero and invariants 2 and 5 would hold for them -trivially. +trivially. With it and the claim the model presumes, each unit would have a +tax credit award while tax credits ran, and an award removes the Pension +Credit child addition (SPC Regs 2002 reg 6(6)(d) and (11)). So the award is +entered as absent (has_tax_credit_award), and the addition is paid in every +year the invariants test. """ from itertools import product @@ -126,7 +130,11 @@ def simulate(units, year): # Both CTC disabled child elements are zero for a unit that is not # eligible, and no generated unit reports a tax credit award. sim.set_input("is_CTC_eligible", year, [True] * len(units)) - sim.input_variables = [*people[0], "is_CTC_eligible"] + # That eligibility and the presumed claim would make a tax credit award, + # which removes the Pension Credit child addition (SPC Regs 2002 reg + # 6(6)(d) and (11)); no award is entered, so the addition is paid. + sim.set_input("has_tax_credit_award", year, [False] * len(units)) + sim.input_variables = [*people[0], "is_CTC_eligible", "has_tax_credit_award"] sim.reset_calculations() return sim From 0fac977b08ef90b169a43e127df859e5ae14e24b Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Sat, 17 Oct 2026 07:35:45 -0400 Subject: [PATCH 5/5] Say the claimant-not-own-child tests exercise the child addition, not pay it Review r4 (GPT-6.1 Sol, on eca1f85b5) noted that "the addition is paid" is too broad: with no award entered, a unit without a child or qualifying young person still gets no addition, as the lone-claimant cases expect. Comment and docstring only. Co-Authored-By: Claude Opus 5.5 --- .../tests/test_claimant_not_own_child_properties.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/policyengine_uk/tests/test_claimant_not_own_child_properties.py b/policyengine_uk/tests/test_claimant_not_own_child_properties.py index 72933793b3..19aba766f6 100644 --- a/policyengine_uk/tests/test_claimant_not_own_child_properties.py +++ b/policyengine_uk/tests/test_claimant_not_own_child_properties.py @@ -42,8 +42,9 @@ trivially. With it and the claim the model presumes, each unit would have a tax credit award while tax credits ran, and an award removes the Pension Credit child addition (SPC Regs 2002 reg 6(6)(d) and (11)). So the award is -entered as absent (has_tax_credit_award), and the addition is paid in every -year the invariants test. +entered as absent (has_tax_credit_award), and the invariants exercise the +addition in both test years: a unit with a child or qualifying young person +gets it, and one without gets none. """ from itertools import product @@ -132,7 +133,8 @@ def simulate(units, year): sim.set_input("is_CTC_eligible", year, [True] * len(units)) # That eligibility and the presumed claim would make a tax credit award, # which removes the Pension Credit child addition (SPC Regs 2002 reg - # 6(6)(d) and (11)); no award is entered, so the addition is paid. + # 6(6)(d) and (11)); no award is entered, so the addition stays under + # test for every unit with a dependant. sim.set_input("has_tax_credit_award", year, [False] * len(units)) sim.input_variables = [*people[0], "is_CTC_eligible", "has_tax_credit_award"] sim.reset_calculations()