diff --git a/changelog.d/claimant-not-own-child-or-qyp.fixed.md b/changelog.d/claimant-not-own-child-or-qyp.fixed.md new file mode 100644 index 0000000000..38cd60fa8d --- /dev/null +++ b/changelog.d/claimant-not-own-child-or-qyp.fixed.md @@ -0,0 +1 @@ +- Stopped counting a benefit unit's own claimant or partner as its child or qualifying young person for Child Benefit, Child Tax Credit and Pension Credit (SSCBA 1992 ss.141 and 143 and Child Benefit (General) Regulations 2006 regs 12-13; CTC Regs 2002 reg. 3; SPC Regs 2002 Sch. IIA para. 3), so a lone 16- to 19-year-old no longer gets Child Benefit, a Child Tax Credit child element or a Pension Credit child addition for themselves. diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/claimant_not_own_child_or_qualifying_young_person.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/claimant_not_own_child_or_qualifying_young_person.yaml new file mode 100644 index 0000000000..ee8d69ebf7 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/claimant_not_own_child_or_qualifying_young_person.yaml @@ -0,0 +1,244 @@ +# A benefit unit's claimant or partner is never its own child or qualifying +# young person for Child Benefit (SSCBA 1992 ss.141, 143(1); Child Benefit +# (General) Regulations 2006 regs 12-13), Child Tax Credit (CTC Regs 2002 +# reg. 3(1) Rule 1, Cases G-H) or Pension Credit (SPC Regs 2002 Sch. IIA +# para. 3). Rates: Child Benefit eldest child £27.05 a week in 2026-27 +# (gov/hmrc/child_benefit/amount/eldest.yaml); Pension Credit child addition +# £69.98 and first-child addition £81.07 a week in 2026-27 +# (gov/dwp/pension_credit/guarantee_credit/child/addition.yaml, +# .../child/first/addition.yaml); CTC child element £3,455 a year in 2024-25 +# (gov/dwp/tax_credits/child_tax_credit/elements/child_element.yaml). + +- name: Case 1, a lone 17-year-old at school is a qualifying young person but not their own. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + young_person: + age: 17 + current_education: UPPER_SECONDARY + is_claimant_or_partner: true + benunits: + benunit: + members: [young_person] + output: + is_qualifying_young_person_for_child_benefit: true + is_qualifying_young_person_for_pension_credit: true + is_child_or_qualifying_young_person_for_child_benefit: false + is_child_or_qualifying_young_person_for_pension_credit: false + child_benefit_entitlement: 0 + child_minimum_guarantee_addition: 0 + +- name: Case 2, a lone parent aged 17 at school gets Child Benefit and the Pension Credit child addition for the baby only. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 17 + current_education: UPPER_SECONDARY + is_parent: true + is_claimant_or_partner: true + baby: + age: 1 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, baby] + output: + is_child_or_qualifying_young_person_for_child_benefit: [false, true] + is_child_or_qualifying_young_person_for_pension_credit: [false, true] + # The baby is the only, so eldest, child: 27.05 * 52. + child_benefit_entitlement: 1_406.60 + # Born after 6 April 2017, so the standard addition: 69.98 * 52. + child_minimum_guarantee_addition: 3_638.96 + +- name: Case 3, no Child Benefit or Pension Credit child addition for a partner aged 18 at school. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 30 + is_claimant_or_partner: true + partner: + age: 18 + current_education: UPPER_SECONDARY + is_claimant_or_partner: true + benunits: + benunit: + members: [claimant, partner] + output: + is_qualifying_young_person_for_child_benefit: [false, true] + is_child_or_qualifying_young_person_for_child_benefit: [false, false] + is_child_or_qualifying_young_person_for_pension_credit: [false, false] + child_benefit_entitlement: 0 + child_minimum_guarantee_addition: 0 + +- name: Case 4, a parent's 17-year-old at school stays the family's qualifying young person. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + young_person: + age: 17 + current_education: UPPER_SECONDARY + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, young_person] + output: + is_child_or_qualifying_young_person_for_child_benefit: [false, true] + is_child_or_qualifying_young_person_for_pension_credit: [false, true] + child_benefit_entitlement: 1_406.60 + # Born before 6 April 2017 and the eldest: 81.07 * 52. + child_minimum_guarantee_addition: 4_215.64 + +- name: Case 5, Child Tax Credit counts no child element for a lone 17-year-old at school. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + young_person: + age: 17 + current_education: UPPER_SECONDARY + is_claimant_or_partner: true + benunits: + benunit: + members: [young_person] + output: + is_qualifying_young_person_for_child_tax_credit: true + is_child_or_qualifying_young_person_for_child_tax_credit: false + is_responsible_for_child_or_qualifying_young_person_for_child_tax_credit: false + CTC_child_element: 0 + +- name: Case 6, Child Tax Credit counts one child element for a parent's 17-year-old at school. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + young_person: + age: 17 + current_education: UPPER_SECONDARY + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, young_person] + output: + is_child_or_qualifying_young_person_for_child_tax_credit: [false, true] + is_responsible_for_child_or_qualifying_young_person_for_child_tax_credit: true + CTC_child_element: 3_455 + +- name: Case 7, Child Tax Credit counts the baby but not the parent aged 17 at school. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 17 + current_education: UPPER_SECONDARY + is_parent: true + is_claimant_or_partner: true + baby: + age: 1 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, baby] + output: + is_child_or_qualifying_young_person_for_child_tax_credit: [false, true] + CTC_child_element: 3_455 + +- name: Case 8, Child Tax Credit counts no child element for a partner aged 18 at school (Case H). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + claimant: + age: 30 + is_claimant_or_partner: true + partner: + age: 18 + current_education: UPPER_SECONDARY + is_claimant_or_partner: true + benunits: + benunit: + members: [claimant, partner] + output: + is_qualifying_young_person_for_child_tax_credit: [false, true] + is_child_or_qualifying_young_person_for_child_tax_credit: [false, false] + CTC_child_element: 0 + +# Known limitation, not a rule of law. When a household is entered with no +# roles, is_claimant_or_partner reads an 18- or 19-year-old at school as the +# head's partner unless the head is at least 16 years older or someone is +# flagged a parent. The model then treats the two as a couple everywhere, +# and so pays no Child Benefit for the 18-year-old. Supplying the roles, or +# flagging the parent, restores it. In law the head can claim for a young +# person who lives with them and is not their partner (SSCBA 1992 +# s.143(1)(a); Child Benefit (General) Regulations 2006 regs 12-13). +- name: Case 9, with no roles supplied an 18-year-old at school 15 years younger than the head is read as their partner. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 33 + student: + age: 18 + current_education: UPPER_SECONDARY + benunits: + benunit: + members: [head, student] + output: + is_claimant_or_partner: [true, true] + is_couple: true + is_child_or_qualifying_young_person_for_child_benefit: [false, false] + child_benefit_entitlement: 0 + +- name: Case 10, the same family with the head flagged a parent gets Child Benefit for the 18-year-old. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 33 + is_parent: true + student: + age: 18 + current_education: UPPER_SECONDARY + benunits: + benunit: + members: [head, student] + output: + is_claimant_or_partner: [true, false] + is_couple: false + is_child_or_qualifying_young_person_for_child_benefit: [false, true] + child_benefit_entitlement: 1_406.60 + +- name: Case 11, the same family with the roles supplied gets Child Benefit for the 18-year-old. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + head: + age: 33 + is_claimant_or_partner: true + student: + age: 18 + current_education: UPPER_SECONDARY + is_claimant_or_partner: false + benunits: + benunit: + members: [head, student] + output: + is_child_or_qualifying_young_person_for_child_benefit: [false, true] + child_benefit_entitlement: 1_406.60 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/general.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/general.yaml index ae7e0d0559..415545c13c 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/general.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/general.yaml @@ -5,14 +5,16 @@ age: 14 output: is_child_or_qualifying_young_person_for_child_benefit: true -- name: Child Benefit child or qualifying young person for student in FE +- name: A student in FE is a Child Benefit qualifying young person, but alone is their own claimant, not their own qualifying young person period: 2020 absolute_error_margin: 0 input: age: 17 current_education: POST_SECONDARY output: - is_child_or_qualifying_young_person_for_child_benefit: true + is_qualifying_young_person_for_child_benefit: true + is_claimant_or_partner: true + is_child_or_qualifying_young_person_for_child_benefit: false - name: Benefits premiums summed period: 2020 absolute_error_margin: 0 diff --git a/policyengine_uk/tests/test_child_and_adult_definitions_properties.py b/policyengine_uk/tests/test_child_and_adult_definitions_properties.py index 8c947e0471..e67b1f7183 100644 --- a/policyengine_uk/tests/test_child_and_adult_definitions_properties.py +++ b/policyengine_uk/tests/test_child_and_adult_definitions_properties.py @@ -6,8 +6,10 @@ approved training, own-benefit receipt and looked-after status, the four programmes' child predicates mean under 16. Their young-person predicates agree with the statutory age/education/entry conditions the model encodes. - Legacy children are Child Benefit children/QYPs other than claimants, - partners and children placed by a local authority; the HBAI fallback and + Child Benefit, Child Tax Credit and Pension Credit children/QYPs exclude + the benefit unit's claimant and partner, as do legacy children, which are + Child Benefit children/QYPs other than children placed by a local + authority; UC's consumers apply their own claimant filter. The HBAI fallback and the claimant-or-partner presumption follow their documented assumptions. An unflagged member at least 16 years younger than a claimant flagged as a parent is never their partner, at any age, unless a member under 20 and @@ -196,12 +198,18 @@ def qualifying_young_person(p, programme): return True -def child_or_young_person(p, programme): +def child_or_young_person(p, programme, claimant=False): # Encoded responsibility filters, without their statutory exceptions: # https://www.legislation.gov.uk/uksi/2013/376/regulation/4 # https://www.legislation.gov.uk/uksi/2002/2007/regulation/3 # https://www.legislation.gov.uk/uksi/2002/1792/schedule/IIA + # No one is responsible for themselves or their partner (SSCBA s.143; + # CTC reg 3 Rule 1 and Cases G-H; SPC Sch IIA para 3). UC's flag keeps the + # claimant and its consumers filter them (UC reg 4). + # https://www.legislation.gov.uk/ukpga/1992/4/section/143 eligible = statutory_child(p["age"]) or qualifying_young_person(p, programme) + if programme != "universal_credit" and claimant: + return False if programme != "child_benefit" and p["is_looked_after_by_local_authority"]: return False return eligible @@ -267,10 +275,8 @@ def legacy_child_or_young_person(p, claimant): # anyone placed by a local authority (IS reg16(4), HB reg21(3)): # https://www.legislation.gov.uk/uksi/1987/1967/regulation/16 # https://www.legislation.gov.uk/uksi/2006/213/regulation/21 - return ( - not claimant - and not p["is_looked_after_by_local_authority"] - and child_or_young_person(p, "child_benefit") + return not p["is_looked_after_by_local_authority"] and child_or_young_person( + p, "child_benefit", claimant ) @@ -310,6 +316,11 @@ def assert_definitions(families, year=YEAR): [non_advanced_education(p) for p in people], year, ) + hbai = [hbai_dependent_child(p, family) for family in families for p in family] + assert_values(sim, "is_hbai_dependent_child", hbai, year) + assert_values(sim, "is_hbai_adult", [not child for child in hbai], year) + claimants = [c for family in families for c in claimants_or_partners(family)] + assert_values(sim, "is_claimant_or_partner", claimants, year) for programme in PROGRAMMES: assert_values(sim, f"is_child_for_{programme}", children, year) assert_values( @@ -321,14 +332,9 @@ def assert_definitions(families, year=YEAR): assert_values( sim, f"is_child_or_qualifying_young_person_for_{programme}", - [child_or_young_person(p, programme) for p in people], + [child_or_young_person(p, programme, c) for p, c in zip(people, claimants)], year, ) - hbai = [hbai_dependent_child(p, family) for family in families for p in family] - assert_values(sim, "is_hbai_dependent_child", hbai, year) - assert_values(sim, "is_hbai_adult", [not child for child in hbai], year) - claimants = [c for family in families for c in claimants_or_partners(family)] - assert_values(sim, "is_claimant_or_partner", claimants, year) # SSCBA s137 and IS reg14 / HB reg19, projected to the encoded family: # https://www.legislation.gov.uk/ukpga/1992/4/section/137 # https://www.legislation.gov.uk/uksi/1987/1967/regulation/14 diff --git a/policyengine_uk/tests/test_claimant_not_own_child_properties.py b/policyengine_uk/tests/test_claimant_not_own_child_properties.py new file mode 100644 index 0000000000..6ebe6e2e94 --- /dev/null +++ b/policyengine_uk/tests/test_claimant_not_own_child_properties.py @@ -0,0 +1,343 @@ +"""A benefit unit's claimant or partner is never its own child or qualifying +young person. + +Child Benefit goes to a person responsible for a child or qualifying young +person, who has them living with them (SSCBA 1992 ss.141 and 143(1)); no one +gets it for their spouse, civil partner or partner (Child Benefit (General) +Regulations 2006 regs 12-13). A tax credit claimant is responsible for a child +or qualifying young person who normally lives with them, never for their own +partner (Child Tax Credit Regulations 2002 reg. 3(1) Rule 1, Cases G-H). A +Pension Credit claimant is responsible for one who normally lives with them, +not for one they live with as a couple (SPC Regs 2002 Sch. IIA para. 3). + +Invariants, for Child Benefit, Child Tax Credit and Pension Credit: + +1. No claimant or partner is the benefit unit's child or qualifying young + person, for any benefit unit, ages and inputs. +2. A claimant's or partner's own age, education, training, benefit receipt + and disability never change the benefit unit's Child Benefit entitlement, + CTC child and disabled child elements, or Pension Credit child addition. +3. Every other member keeps the programme's status test: they are the + unit's child or qualifying young person exactly when they are a child or + qualifying young person for the programme (and, for CTC and Pension + Credit, not looked after by a local authority). This restates the + formula from the status variables, to hold the composition in place; + invariants 2 and 4 and the oracle in + test_child_and_adult_definitions_properties.py are the independent + checks. +4. Child Benefit entitlement equals an independent count: the eldest such + member at the eldest-child rate, each other at the additional rate, for + 52 weeks. +5. The disability amounts count dependants only. With a disabled child and + a disabled qualifying young person in the unit, the CTC disabled and + severely disabled child elements and the Pension Credit disabled child + additions equal the rates times the disabled dependants, whatever the + claimant's and partner's own disability. + +The household structure is supplied (is_claimant_or_partner), so the tests +exercise the programme rules and not the structural presumption, which +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. +""" + +from itertools import product +from unittest.mock import patch + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.tax_benefit_system import system +from policyengine_uk.variables.household.demographic.highest_education import ( + EducationType, +) + +YEARS = (2024, 2026) +_PROPERTY_SYSTEM = system.clone() +EDUCATIONS = tuple(item.name for item in EducationType) +PROGRAMMES = ("child_benefit", "child_tax_credit", "pension_credit") +CHILD_AMOUNTS = ( + "child_benefit_entitlement", + "CTC_child_element", + "CTC_disabled_child_element", + "CTC_severely_disabled_child_element", + "child_minimum_guarantee_addition", +) +SETTINGS = settings( + max_examples=25, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + +member = st.fixed_dictionaries( + { + "age": st.integers(0, 25), + "current_education": st.sampled_from(EDUCATIONS), + "is_in_approved_training": st.booleans(), + "receives_benefits_in_own_right": st.booleans(), + "is_looked_after_by_local_authority": st.booleans(), + "is_disabled_for_benefits": st.booleans(), + "is_severely_disabled_for_benefits": st.booleans(), + # Pension Credit reads a child's disability from these (Sch. IIA + # para. 9), not from the two flags above. + "dla": st.sampled_from([0, 3_000]), + "receives_highest_dla_sc": st.booleans(), + "age_started_or_accepted_current_education_or_training": st.sampled_from( + [17, 1000] + ), + } +) + + +@st.composite +def benefit_units(draw): + """A claimant, an optional partner and up to four other members, all with + arbitrary ages and inputs.""" + claimants = draw(st.lists(member, min_size=1, max_size=2)) + others = draw(st.lists(member, max_size=4)) + return [dict(p, is_claimant_or_partner=True) for p in claimants] + [ + dict(p, is_claimant_or_partner=False) for p in others + ] + + +def simulate(units, year): + situation = {"people": {}, "benunits": {}, "households": {}} + people = [] + for i, unit in enumerate(units): + names = [] + for j, inputs in enumerate(unit): + name = f"p{i}_{j}" + situation["people"][name] = {} + people.append(inputs) + names.append(name) + situation["benunits"][f"b{i}"] = {"members": names} + situation["households"][f"h{i}"] = {"members": names} + with patch( + "policyengine_uk.simulation.CountryTaxBenefitSystem", + side_effect=_PROPERTY_SYSTEM.clone, + ): + sim = Simulation(situation=situation) + for variable in people[0]: + sim.set_input(variable, year, [p[variable] for p in people]) + # 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"] + sim.reset_calculations() + return sim + + +def split(values, units): + out, offset = [], 0 + for unit in units: + out.append(values[offset : offset + len(unit)]) + offset += len(unit) + return out + + +@SETTINGS +@given(st.lists(benefit_units(), min_size=1, max_size=6), st.sampled_from(YEARS)) +def test_claimants_are_never_their_units_children_and_others_keep_status(units, year): + sim = simulate(units, year) + claimant = np.array([p["is_claimant_or_partner"] for u in units for p in u]) + looked_after = np.array( + [p["is_looked_after_by_local_authority"] for u in units for p in u] + ) + for programme in PROGRAMMES: + family = sim.calculate( + f"is_child_or_qualifying_young_person_for_{programme}", year + ) + status = sim.calculate(f"is_child_for_{programme}", year) | sim.calculate( + f"is_qualifying_young_person_for_{programme}", year + ) + # Invariant 1. + assert not (family & claimant).any(), programme + # Invariant 3. + expected = status & ~claimant + if programme != "child_benefit": + expected &= ~looked_after + np.testing.assert_array_equal(family, expected, err_msg=programme) + + +@SETTINGS +@given( + st.lists( + st.tuples(benefit_units(), st.lists(member, min_size=2, max_size=2)), + min_size=1, + max_size=5, + ), + st.sampled_from(YEARS), +) +def test_claimants_own_circumstances_never_change_child_amounts(pairs, year): + # Invariant 2: each unit appears twice, the second time with its claimant + # and any partner redrawn and every other member unchanged. + original = [unit for unit, _ in pairs] + redrawn = [] + for unit, replacements in pairs: + copy, k = [], 0 + for p in unit: + if p["is_claimant_or_partner"]: + copy.append(dict(replacements[k], is_claimant_or_partner=True)) + k += 1 + else: + copy.append(p) + redrawn.append(copy) + sim = simulate(original + redrawn, year) + n = len(original) + for variable in CHILD_AMOUNTS: + values = sim.calculate(variable, year) + np.testing.assert_allclose(values[:n], values[n:], err_msg=variable) + + +@SETTINGS +@given(st.lists(benefit_units(), min_size=1, max_size=6), st.sampled_from(YEARS)) +def test_child_benefit_entitlement_counts_only_other_members(units, year): + # Invariant 4, from status inputs and the weekly rates alone. + sim = simulate(units, year) + rates = sim.tax_benefit_system.parameters(f"{year}-06-01").gov.hmrc.child_benefit + status = sim.calculate("is_child_for_child_benefit", year) | sim.calculate( + "is_qualifying_young_person_for_child_benefit", year + ) + entitlement = sim.calculate("child_benefit_entitlement", year) + for unit, eligible, actual in zip(units, split(status, units), entitlement): + counted = [ + e and not p["is_claimant_or_partner"] for p, e in zip(unit, eligible) + ] + number = sum(counted) + weekly = ( + 0 + if number == 0 + else rates.amount.eldest + (number - 1) * rates.amount.additional + ) + np.testing.assert_allclose(actual, weekly * 52, rtol=1e-6) + + +LEVELS = ("none", "standard", "severe") + + +def disability(level): + return { + "is_disabled_for_benefits": level != "none", + "is_severely_disabled_for_benefits": level == "severe", + "dla": 0 if level == "none" else 3_000, + "receives_highest_dla_sc": level == "severe", + } + + +def test_lone_qualifying_young_person_gets_no_child_amount_for_themselves(): + # Exhaustive over ages 15-20, every education value, training flag and + # disability level: a lone claimant gets nothing for themselves, whatever + # their status. + units = [ + [ + { + "age": age, + "current_education": education, + "is_in_approved_training": training, + "receives_benefits_in_own_right": False, + "is_looked_after_by_local_authority": False, + **disability(level), + "age_started_or_accepted_current_education_or_training": 17, + "is_claimant_or_partner": True, + } + ] + for age in range(15, 21) + for education in EDUCATIONS + for training in (False, True) + for level in LEVELS + ] + for year in YEARS: + sim = simulate(units, year) + status = sim.calculate("is_qualifying_young_person_for_child_benefit", year) + # Not vacuous: some of these claimants are qualifying young persons. + assert status.any() + for variable in CHILD_AMOUNTS: + np.testing.assert_array_equal( + sim.calculate(variable, year), 0, err_msg=variable + ) + + +def test_disability_amounts_count_disabled_dependants_and_never_claimants(): + # Invariant 5. A claimant aged 17 at school or 40, a partner aged 18 at + # school, a child of 10 and a 17-year-old at school, over every + # combination of the four members' disability levels. The claimant at 17 + # and the partner are qualifying young persons by status. + def unit_member(age, level, claimant): + return { + "age": age, + "current_education": "UPPER_SECONDARY" if age < 20 else "NOT_IN_EDUCATION", + "is_in_approved_training": False, + "receives_benefits_in_own_right": False, + "is_looked_after_by_local_authority": False, + **disability(level), + "age_started_or_accepted_current_education_or_training": 17, + "is_claimant_or_partner": claimant, + } + + cases = [ + (claimant_age, levels) + for claimant_age in (17, 40) + for levels in product(LEVELS, repeat=4) + ] + units = [ + [ + unit_member(claimant_age, claimant, True), + unit_member(18, partner, True), + unit_member(10, child, False), + unit_member(17, young_person, False), + ] + for claimant_age, (claimant, partner, child, young_person) in cases + ] + dependants = [levels[2:] for _, levels in cases] + disabled = np.array([sum(d != "none" for d in ds) for ds in dependants]) + severe = np.array([sum(d == "severe" for d in ds) for ds in dependants]) + for year in YEARS: + sim = simulate(units, year) + gov = sim.tax_benefit_system.parameters(f"{year}-06-01").gov + elements = gov.dwp.tax_credits.child_tax_credit.elements + child = gov.dwp.pension_credit.guarantee_credit.child + # Sch. IIA para. 10: the 17-year-old is the eldest, born before 6 + # April 2017, so takes the first-child amount; the child of 10 takes + # the standard one. + weekly_addition = np.array( + [ + child.first.addition + + child.addition + + sum( + { + "none": 0, + "standard": child.disability.addition, + "severe": child.disability.severe.addition, + }[d] + for d in ds + ) + for ds in dependants + ] + ) + expected = { + "child_benefit_entitlement": np.full( + len(units), + ( + gov.hmrc.child_benefit.amount.eldest + + gov.hmrc.child_benefit.amount.additional + ) + * 52, + ), + "CTC_child_element": np.full(len(units), 2 * elements.child_element), + "CTC_disabled_child_element": elements.dis_child_element * disabled, + "CTC_severely_disabled_child_element": ( + elements.severe_dis_child_element * severe + ), + "child_minimum_guarantee_addition": weekly_addition * 52, + } + assert set(expected) == set(CHILD_AMOUNTS) + for variable, amounts in expected.items(): + # Not vacuous: the amount is paid for a disabled dependant. + assert amounts.max() > 0, variable + np.testing.assert_allclose( + sim.calculate(variable, year), amounts, rtol=1e-6, err_msg=variable + ) diff --git a/policyengine_uk/variables/gov/dwp/is_child_or_qualifying_young_person_for_child_tax_credit.py b/policyengine_uk/variables/gov/dwp/is_child_or_qualifying_young_person_for_child_tax_credit.py index d765404b1a..fd0a472e49 100644 --- a/policyengine_uk/variables/gov/dwp/is_child_or_qualifying_young_person_for_child_tax_credit.py +++ b/policyengine_uk/variables/gov/dwp/is_child_or_qualifying_young_person_for_child_tax_credit.py @@ -2,11 +2,34 @@ class is_child_or_qualifying_young_person_for_child_tax_credit(Variable): + """A child or qualifying young person the benefit unit is responsible for + under the Child Tax Credit rules. + + A claimant is responsible for a child or qualifying young person who + normally lives with them (Child Tax Credit Regulations 2002 reg. 3(1) + Rule 1), which a claimant cannot be to themselves. No one is responsible + for a qualifying young person who is the responsible person's partner, or + who lives with a partner not in full-time education or approved training + (Rule 4, Cases G and H). Nor for one who claims and is awarded child tax + credit in their own right for a child they are responsible for (Case D), + or who claims and receives working tax credit in their own right + (Case F). So the benefit unit's claimant and partner (Tax Credits Act + 2002 s.3) are never its children or qualifying young persons. + + The model also leaves out every child or qualifying young person looked + after by a local authority. That is broader than the law. Case A needs + accommodation provided under the children's legislation with its cost + met wholly or partly from local authority or other public funds, and + Case B a placement for adoption with a maintenance payment. The model + has no input for either condition. + """ + value_type = bool entity = Person label = "Child or qualifying young person for Child Tax Credit" definition_period = YEAR reference = [ + "https://www.legislation.gov.uk/ukpga/2002/21/section/3", "https://www.legislation.gov.uk/ukpga/2002/21/section/8", "https://www.legislation.gov.uk/uksi/2002/2007/regulation/3", "https://www.legislation.gov.uk/uksi/2002/2007/regulation/5", @@ -16,6 +39,8 @@ def formula(person, period, parameters): child_or_qualifying_young_person = person( "is_child_for_child_tax_credit", period ) | person("is_qualifying_young_person_for_child_tax_credit", period) - return child_or_qualifying_young_person & ~person( - "is_looked_after_by_local_authority", period + return ( + child_or_qualifying_young_person + & ~person("is_claimant_or_partner", period) + & ~person("is_looked_after_by_local_authority", period) ) diff --git a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/is_child_or_qualifying_young_person_for_pension_credit.py b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/is_child_or_qualifying_young_person_for_pension_credit.py index fed5985857..2876ddb1f1 100644 --- a/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/is_child_or_qualifying_young_person_for_pension_credit.py +++ b/policyengine_uk/variables/gov/dwp/pension_credit/guarantee_credit/minimum_guarantee/additional/is_child_or_qualifying_young_person_for_pension_credit.py @@ -2,6 +2,24 @@ class is_child_or_qualifying_young_person_for_pension_credit(Variable): + """A child or qualifying young person the benefit unit is responsible for + under Pension Credit Schedule IIA. + + A claimant is responsible for a child or qualifying young person who + normally lives with them, which a claimant cannot be to themselves, but + not for a qualifying young person they live with as a couple (State + Pension Credit Regulations 2002 Sch. IIA para. 3(1)-(2)). So the benefit + unit's claimant and partner are never its children or qualifying young + persons, whatever their age or education. + + The model also leaves out every child or qualifying young person looked + after by a local authority (para. 4(1)). That is broader than the law: + para. 4(3) keeps the claimant responsible during a planned short break + for respite, while the child is placed with or still lives with a parent + or a person with parental responsibility, and during a placement for + adoption. The model has no input for those exceptions. + """ + value_type = bool entity = Person label = "Child or qualifying young person for Pension Credit" @@ -12,6 +30,8 @@ def formula(person, period, parameters): child_or_qualifying_young_person = person( "is_child_for_pension_credit", period ) | person("is_qualifying_young_person_for_pension_credit", period) - return child_or_qualifying_young_person & ~person( - "is_looked_after_by_local_authority", period + return ( + child_or_qualifying_young_person + & ~person("is_claimant_or_partner", period) + & ~person("is_looked_after_by_local_authority", period) ) diff --git a/policyengine_uk/variables/gov/hmrc/is_child_or_qualifying_young_person_for_child_benefit.py b/policyengine_uk/variables/gov/hmrc/is_child_or_qualifying_young_person_for_child_benefit.py index a39fc627c7..1fe0ec52c7 100644 --- a/policyengine_uk/variables/gov/hmrc/is_child_or_qualifying_young_person_for_child_benefit.py +++ b/policyengine_uk/variables/gov/hmrc/is_child_or_qualifying_young_person_for_child_benefit.py @@ -2,17 +2,37 @@ class is_child_or_qualifying_young_person_for_child_benefit(Variable): + """A child or qualifying young person whom the benefit unit can claim + Child Benefit for. + + Child Benefit goes to a person responsible for a child or qualifying + young person (SSCBA 1992 s.141): someone who has them living with them or + contributes to their keep (s.143(1)). A person is not responsible for + themselves, and no one gets Child Benefit for a qualifying young person + who is their spouse, civil partner or partner (Child Benefit (General) + Regulations 2006 regs 12 and 13). So the benefit unit's claimant and + partner are never its children or qualifying young persons, whatever + their age or education. Someone responsible for them in another benefit + unit is not modelled. + """ + value_type = bool entity = Person label = "Child or qualifying young person for Child Benefit" definition_period = YEAR reference = [ + "https://www.legislation.gov.uk/ukpga/1992/4/section/141", "https://www.legislation.gov.uk/ukpga/1992/4/section/142", "https://www.legislation.gov.uk/ukpga/1992/4/section/143", "https://www.legislation.gov.uk/uksi/2006/223", + "https://www.legislation.gov.uk/uksi/2006/223/regulation/12", + "https://www.legislation.gov.uk/uksi/2006/223/regulation/13", ] def formula(person, period, parameters): - return person("is_child_for_child_benefit", period) | person( - "is_qualifying_young_person_for_child_benefit", period + child_or_qualifying_young_person = person( + "is_child_for_child_benefit", period + ) | person("is_qualifying_young_person_for_child_benefit", period) + return child_or_qualifying_young_person & ~person( + "is_claimant_or_partner", period )