From a27a539ce6fc6bc4910326960dcbd5cb97ceb8ec Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 2 Oct 2026 07:52:06 -0400 Subject: [PATCH] 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