diff --git a/changelog.d/cb-qyp-school-leaver-routes.fixed.md b/changelog.d/cb-qyp-school-leaver-routes.fixed.md new file mode 100644 index 0000000000..b8cf3f0d0c --- /dev/null +++ b/changelog.d/cb-qyp-school-leaver-routes.fixed.md @@ -0,0 +1 @@ +Added the Child Benefit qualifying young person conditions for young people who have left education (Child Benefit (General) Regulations 2006 regs 4, 5 and 7), which the Housing Benefit, legacy benefit and Council Tax Reduction young persons follow: a 16-year-old counts until the 31 August after their 16th birthday, in any year that starts before it, two new inputs place the 20-week extension period and the terminal date, and the model pays no Child Benefit for a young person who qualifies only through the terminal date and works 24 hours or more a week (reg. 7(3)). A young person who reports their own Universal Credit, Employment and Support Allowance or income-based Jobseeker's Allowance, or their own Income Support while DWP pays it, is not one, on any route (reg. 8). diff --git a/changelog.d/ctc-qyp-school-leaver-routes.fixed.md b/changelog.d/ctc-qyp-school-leaver-routes.fixed.md new file mode 100644 index 0000000000..091fbd5db6 --- /dev/null +++ b/changelog.d/ctc-qyp-school-leaver-routes.fixed.md @@ -0,0 +1 @@ +Counted a 16-year-old who has left education as a Child Tax Credit qualifying young person until the 31 August after their 16th birthday, in any year that starts before it (Child Tax Credit Regulations 2002 reg. 4), and added an input for the 20 weeks after leaving while registered for work or training (reg. 5(3)(b)). Remunerative work of 24 hours a week ends both for a young person who has left education (reg. 5(4)(a)), and Income Support received by the young person ends every route (reg. 5(4)(c)). diff --git a/policyengine_uk/parameters/gov/dwp/tax_credits/child_tax_credit/eligibility/extension_period_age_limit.yaml b/policyengine_uk/parameters/gov/dwp/tax_credits/child_tax_credit/eligibility/extension_period_age_limit.yaml new file mode 100644 index 0000000000..2266a1fe0c --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/tax_credits/child_tax_credit/eligibility/extension_period_age_limit.yaml @@ -0,0 +1,9 @@ +description: Age limit below which a person who has left education or training may be a Child Tax Credit qualifying young person for 20 weeks while registered for work or training. +values: + 0001-01-01: 18 +metadata: + unit: year + label: Child Tax Credit extension period age limit + reference: + - title: The Child Tax Credit Regulations 2002 reg. 5(3)(b) + href: https://www.legislation.gov.uk/uksi/2002/2007/regulation/5 diff --git a/policyengine_uk/parameters/gov/dwp/tax_credits/child_tax_credit/eligibility/remunerative_work_hours.yaml b/policyengine_uk/parameters/gov/dwp/tax_credits/child_tax_credit/eligibility/remunerative_work_hours.yaml new file mode 100644 index 0000000000..dc614090d1 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/tax_credits/child_tax_credit/eligibility/remunerative_work_hours.yaml @@ -0,0 +1,9 @@ +description: Weekly hours of paid work from which a person is in remunerative work for Child Tax Credit rules. +values: + 0001-01-01: 24 +metadata: + unit: hour + label: Child Tax Credit remunerative work hours + reference: + - title: The Child Tax Credit Regulations 2002 reg. 2(1), "remunerative work" + href: https://www.legislation.gov.uk/uksi/2002/2007/regulation/2 diff --git a/policyengine_uk/parameters/gov/hmrc/child_benefit/eligibility/extension_period_age_limit.yaml b/policyengine_uk/parameters/gov/hmrc/child_benefit/eligibility/extension_period_age_limit.yaml new file mode 100644 index 0000000000..54c952537e --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/child_benefit/eligibility/extension_period_age_limit.yaml @@ -0,0 +1,9 @@ +description: Age limit below which a person who has left education or training may be a Child Benefit qualifying young person during an extension period. +values: + 0001-01-01: 18 +metadata: + unit: year + label: Child Benefit extension period age limit + reference: + - title: Child Benefit (General) Regulations 2006 reg. 5(1) + href: https://www.legislation.gov.uk/uksi/2006/223/regulation/5 diff --git a/policyengine_uk/parameters/gov/hmrc/child_benefit/eligibility/remunerative_work_hours.yaml b/policyengine_uk/parameters/gov/hmrc/child_benefit/eligibility/remunerative_work_hours.yaml new file mode 100644 index 0000000000..e6271ff838 --- /dev/null +++ b/policyengine_uk/parameters/gov/hmrc/child_benefit/eligibility/remunerative_work_hours.yaml @@ -0,0 +1,9 @@ +description: Weekly hours of paid work from which a person is in remunerative work for Child Benefit rules. +values: + 0001-01-01: 24 +metadata: + unit: hour + label: Child Benefit remunerative work hours + reference: + - title: Child Benefit (General) Regulations 2006 reg. 1(3), "remunerative work" + href: https://www.legislation.gov.uk/uksi/2006/223/regulation/1 diff --git a/policyengine_uk/tests/income_support_reforms.py b/policyengine_uk/tests/income_support_reforms.py new file mode 100644 index 0000000000..1692439402 --- /dev/null +++ b/policyengine_uk/tests/income_support_reforms.py @@ -0,0 +1,10 @@ +"""Reforms that policy YAML tests apply by dotted path (the `reforms` key).""" + +from policyengine_core.reforms import Reform + + +class neutralise_income_support(Reform): + """A reform that removes Income Support by neutralising income_support.""" + + def apply(self): + self.neutralize_variable("income_support") diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/child_tax_credit_school_leaver_routes.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/child_tax_credit_school_leaver_routes.yaml new file mode 100644 index 0000000000..5b31eb0678 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/child_tax_credit_school_leaver_routes.yaml @@ -0,0 +1,536 @@ +# Child Tax Credit qualifying young persons who have left education: Child +# Tax Credit Regulations 2002 reg. 4 (from the 16th birthday until the 31 +# August that next follows, with no education condition), reg. 5(3)(b) (20 +# weeks after leaving, under 18, while registered for work or training) and +# the second condition in reg. 5(4), which applies to both: not in +# remunerative work (24 hours a week or more, reg. 2(1)) after leaving, and +# not receiving a benefit in their own right. +# +# The child element was £3,455 in 2024-25, the last year of tax credits +# (gov/dwp/tax_credits/child_tax_credit/elements/child_element.yaml). +# +# The child element needs an existing claim that a Universal Credit claim has +# not ended (is_CTC_eligible): each case that reads it gives the claimant a +# reported award and the family no Universal Credit claim. + +- name: Case 1, a 16-year-old who has left education is a qualifying young person for the year (reg. 4(1)). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, true] + 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 2, a 16-year-old who has left education and works 24 hours a week is not a qualifying young person (reg. 5(4)(a)). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + weekly_hours: 24 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_in_remunerative_work_for_child_tax_credit: [false, true] + is_qualifying_young_person_for_child_tax_credit: [false, false] + CTC_child_element: 0 + # Child Benefit reg. 4 has no remunerative work bar. + is_qualifying_young_person_for_child_benefit: [false, true] + +- name: Case 3, working 23 hours a week does not end the period. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + weekly_hours: 23 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, true] + CTC_child_element: 3_455 + +- name: Case 4, remunerative work does not matter for a 16-year-old still at school. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + pupil: + age: 16 + current_education: UPPER_SECONDARY + weekly_hours: 24 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, pupil] + would_claim_uc: false + output: + # Reg. 5(4)(a) applies to a person "having ceased" education or training. + is_qualifying_young_person_for_child_tax_credit: [false, true] + CTC_child_element: 3_455 + +- name: Case 5, a 16-year-old apprentice still on a college course and working 30 hours a week is a qualifying young person. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + apprentice: + age: 16 + current_education: POST_SECONDARY + is_apprentice: true + weekly_hours: 30 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, apprentice] + would_claim_uc: false + output: + # Education "by virtue of his employment" is left out of the first + # condition (reg. 5(3)(a)(ii)), which reg. 4(2) lifts. The apprentice has + # not "ceased to receive full-time education" (reg. 5(4)(a), 5(5)), so + # the remunerative work bar does not apply. + is_qualifying_young_person_for_child_tax_credit: [false, true] + CTC_child_element: 3_455 + +- name: Case 6, born 15 September 2007, the period runs to 31 August 2024. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 17 + date_of_birth: 20070915 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, true] + CTC_child_element: 3_455 + +- name: Case 7, born 31 August 2007, reg. 4(1A) gives no period to the next 31 August. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 17 + date_of_birth: 20070831 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, false] + CTC_child_element: 0 + +- name: Case 8, born 31 August 2008, the year in which they are a child until 30 August counts. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + date_of_birth: 20080831 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, true] + CTC_child_element: 3_455 + +- name: Case 9, a 16-year-old receiving a benefit in their own right is not a qualifying young person (reg. 5(4)(c)). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + receives_benefits_in_own_right: true + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, false] + CTC_child_element: 0 + +- name: Case 10, a 17-year-old who has left education is not a qualifying young person by default. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 17 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, false] + CTC_child_element: 0 + +- name: Case 11, a 17-year-old in the 20 weeks after leaving, registered for work or training, is a qualifying young person (reg. 5(3)(b)). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 17 + current_education: NOT_IN_EDUCATION + is_in_child_tax_credit_extension_period: true + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, true] + CTC_child_element: 3_455 + +- name: Case 12, the 20 weeks end if the 17-year-old starts remunerative work. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 17 + current_education: NOT_IN_EDUCATION + is_in_child_tax_credit_extension_period: true + weekly_hours: 24 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, false] + CTC_child_element: 0 + +- name: Case 13, the 20 weeks are for under-18s only. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 18 + current_education: NOT_IN_EDUCATION + is_in_child_tax_credit_extension_period: true + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, false] + CTC_child_element: 0 + +- name: Case 14, a lone 16-year-old who has left education is not their own qualifying young person. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + school_leaver: + child_tax_credit_reported: 1 + age: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + benunit: + members: [school_leaver] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: true + is_child_or_qualifying_young_person_for_child_tax_credit: false + CTC_child_element: 0 + +- name: Case 15, a 16-year-old in full-time advanced education working 24 hours a week is a qualifying young person (reg. 4(2), 5(4)(a), 5(5)). + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + student: + age: 16 + date_of_birth: 20080101 + current_education: TERTIARY + weekly_hours: 24 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, student] + would_claim_uc: false + output: + # Advanced education fails the first condition, which reg. 4(2) lifts. + # It is still full-time education (reg. 5(5)), so the student has not + # "ceased" it and the remunerative work bar does not apply. + is_qualifying_young_person_for_child_tax_credit: [false, true] + CTC_child_element: 3_455 + +- name: Case 16, the same advanced student aged 17, outside the reg. 4 period, is not a qualifying young person. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + student: + age: 17 + date_of_birth: 20070101 + current_education: TERTIARY + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, student] + would_claim_uc: false + output: + is_qualifying_young_person_for_child_tax_credit: [false, false] + CTC_child_element: 0 + +- name: Case 17, a 16-year-old apprentice no longer on any course and working 30 hours a week is not a qualifying young person. + period: 2024 + absolute_error_margin: 0.01 + input: + people: + parent: + child_tax_credit_reported: 1 + age: 40 + is_parent: true + is_claimant_or_partner: true + apprentice: + age: 16 + current_education: NOT_IN_EDUCATION + is_apprentice: true + weekly_hours: 30 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, apprentice] + would_claim_uc: false + output: + # Ceased full-time education and in remunerative work (reg. 5(4)(a)). + is_qualifying_young_person_for_child_tax_credit: [false, false] + CTC_child_element: 0 + +# Cases 18 to 20: the Income Support bar in reg. 5(4)(c), which reg. 4(2) +# keeps for the 16th-birthday period. 2018-19 rates: child element £2,780 and +# family element £545 a year (child_element.yaml, family_element.yaml); PIP +# daily living standard rate £57.30 a week; Carer's Allowance £64.60 a week; +# Child Benefit eldest rate £20.70 a week; colour TV licence £150.50. Cases +# 18 and 19 set the cost-of-living payment, which DWP first paid in 2022, to +# zero. + +- name: Case 18, a 16-year-old school leaver who receives Income Support in their own right is not a qualifying young person (reg. 4(2), reg. 5(4)(c)). + period: 2018 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + pip_dl_category: STANDARD + child_tax_credit_reported: 1 + carer: + age: 16 + date_of_birth: 20020115 + current_education: NOT_IN_EDUCATION + care_hours: 35 + income_support_reported: 3_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, carer] + would_claim_uc: false + households: + household: + members: [parent, carer] + # DWP first paid cost-of-living payments in 2022. + cost_of_living_support_payment: 0 + output: + # Born 15 January 2002: the reg. 4 period runs to 31 August 2018. + is_before_first_september_following_16th_birthday: [false, true] + # Not the claimant or partner, so their own report while Income Support + # is in payment. + is_on_income_support: [false, true] + is_qualifying_young_person_for_child_tax_credit: [false, false] + # No child or qualifying young person, so no CTC. + child_tax_credit: 0 + # Child Benefit reg. 8(2)(a) ends their Child Benefit status too. + is_qualifying_young_person_for_child_benefit: [false, false] + child_benefit: 0 + # PIP 57.30 x 52 = 2,979.60; Carer's Allowance 64.60 x 52 = 3,359.20; + # less TV licence 150.50: 6,188.30. The model calculates Income Support + # only for the claimant's family, so the 16-year-old's own 3,000 is in no + # income here. + household_net_income: 6_188.30 + +- name: Case 19, the same family without Income Support for the 16-year-old gets CTC until 31 August 2018 (reg. 4(1)). + period: 2018 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + pip_dl_category: STANDARD + child_tax_credit_reported: 1 + carer: + age: 16 + date_of_birth: 20020115 + current_education: NOT_IN_EDUCATION + care_hours: 35 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, carer] + would_claim_uc: false + households: + household: + members: [parent, carer] + # DWP first paid cost-of-living payments in 2022. + cost_of_living_support_payment: 0 + output: + is_before_first_september_following_16th_birthday: [false, true] + is_on_income_support: [false, false] + is_qualifying_young_person_for_child_tax_credit: [false, true] + # No income: child element 2,780 + family element 545 = 3,325. + child_tax_credit: 3_325 + # 20.70 x 52. + child_benefit: 1_076.40 + # Case 18's 6,188.30 + Child Benefit 1,076.40 + 3,325 = 10,589.70. + household_net_income: 10_589.70 + +- name: Case 20, a 17-year-old at school who receives Income Support in their own right is not a qualifying young person (reg. 5(3)(a), 5(4)(c)). + period: 2018 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + child_tax_credit_reported: 1 + student: + age: 17 + date_of_birth: 20010115 + current_education: UPPER_SECONDARY + income_support_reported: 3_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, student] + would_claim_uc: false + output: + # The first condition holds: full-time non-advanced education begun + # before 19. + meets_qualifying_young_person_education_or_training_condition_for_child_tax_credit: [false, true] + is_before_first_september_following_16th_birthday: [false, false] + is_on_income_support: [false, true] + is_qualifying_young_person_for_child_tax_credit: [false, false] + child_tax_credit: 0 diff --git a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income_childcare_element.yaml b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income_childcare_element.yaml index 86edb6a8d1..896eba2ba6 100644 --- a/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income_childcare_element.yaml +++ b/policyengine_uk/tests/policy/baseline/finance/benefit/family/housing_benefit/applicable_income/housing_benefit_applicable_income_childcare_element.yaml @@ -1534,8 +1534,9 @@ num_relevant_children_for_housing_benefit_childcare: 0 housing_benefit_applicable_income_childcare_element: 0 -- name: Disabled 16-year-old outside education and training is not a relevant young person +- name: Disabled 16-year-old outside education and training is a relevant young person until the 31 August after their 16th birthday period: 2025 + absolute_error_margin: 0.01 input: people: claimant: @@ -1558,9 +1559,36 @@ housing_benefit_specified_or_temporary_accommodation_disregard: 0 working_tax_credit: 0 child_tax_credit: 0 + output: + # A 16-year-old who has left education is a young person until that 31 + # August (Child Benefit (General) Regulations 2006 reg. 4; HB reg 19(1)), + # in any year that starts before it. HB reg 28(6) then counts a disabled + # one: min(9,000, one-child cap * 52). + is_child_or_young_person_for_legacy_benefits: [false, true] + num_relevant_children_for_housing_benefit_childcare: 1 + housing_benefit_applicable_income_childcare_element: 9_000 + +- name: Disabled 16-year-old outside education and training is not a relevant young person after that 31 August + period: 2025 + input: + people: + claimant: + age: 40 + is_parent: true + childcare_expenses: 9_000 + young_person: + age: 16 + is_disabled_for_benefits: true + is_in_non_advanced_education: false + is_in_approved_training: false + is_before_first_september_following_16th_birthday: false + benunits: + benunit: + members: [claimant, young_person] output: # HB reg 28(6) requires a child of the family, including a qualifying # young person; the disability age extension does not create that status. + is_child_or_young_person_for_legacy_benefits: [false, false] num_relevant_children_for_housing_benefit_childcare: 0 housing_benefit_applicable_income_childcare_element: 0 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_specified_benefits.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_specified_benefits.yaml index 9327b3d862..75ad125eeb 100644 --- a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_specified_benefits.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/benefit_cap_specified_benefits.yaml @@ -821,13 +821,15 @@ members: [parent, sixteen] region: NORTH_EAST output: - is_child_or_young_person_for_legacy_benefits: [false, false] + # HB reg 19(1) requires a Child Benefit "qualifying young person". A + # 16-year-old who has left education is one until the 31 August after + # their 16th birthday (Child Benefit (General) Regulations 2006 reg. 4), + # so they are the family's young person for Housing Benefit too. + is_child_or_young_person_for_legacy_benefits: [false, true] # UC reg 5(1)(a): "up to, but not including, the 1st September following # their 16th birthday". Annual ages count the 16-year-old for the year. - # HB reg 19(1) requires a Child Benefit "qualifying young person"; - # this person is outside education and is not one in the model. is_uc_benefit_cap_exempt_specified_benefit: true - is_housing_benefit_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A 19-year-old outside education is not a young person, so their DLA does not lift the cap period: 2025 @@ -1188,7 +1190,7 @@ housing_benefit: 30_000 absolute_error_margin: 1 -- name: A 16-year-old out of education with armed forces independence payment does not lift the cap +- name: A 16-year-old out of education with armed forces independence payment lifts the Housing Benefit cap, not Universal Credit's period: 2025 input: people: @@ -1207,10 +1209,13 @@ members: [parent, sixteen] region: NORTH_EAST output: - # Not a Housing Benefit young person (not in education), and Universal - # Credit counts the payment for a claimant only (UC Regs 2013 reg 2). + # A Housing Benefit young person until the 31 August after the 16th + # birthday (Child Benefit (General) Regulations 2006 reg. 4), whose armed + # forces independence payment lifts the cap (HB Regs 2006 reg. 75F(1)). + # Universal Credit counts the payment for a claimant only (UC Regs 2013 + # reg 2). is_uc_benefit_cap_exempt_specified_benefit: false - is_housing_benefit_benefit_cap_exempt_specified_benefit: false + is_housing_benefit_benefit_cap_exempt_specified_benefit: true - name: A 16-year-old receiving benefits in their own right is not a young person, so their DLA does not lift the cap period: 2025 diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/child_benefit/child_benefit_school_leaver_routes.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/child_benefit/child_benefit_school_leaver_routes.yaml new file mode 100644 index 0000000000..571728fc4d --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/child_benefit/child_benefit_school_leaver_routes.yaml @@ -0,0 +1,794 @@ +# The Child Benefit qualifying young person conditions for a young person who +# has left education: Child Benefit (General) Regulations 2006 reg. 4 (until +# the 31 August after the 16th birthday), reg. 5 (the 20-week extension +# period for a 16- or 17-year-old), reg. 7 (until the terminal date) and +# reg. 7(3) (no Child Benefit by virtue of reg. 7 in a week of remunerative +# work). Reg. 2(2): a qualifying young person until the last applicable +# condition ceases to be met. Reg. 8: not while receiving a benefit in their +# own right, on any route. Cases 17, 18, 27 and 28 cover reported Universal +# Credit, ESA and JSA awards (reg. 8(2)(b), (e) and (f)), and Cases 19 to 26 +# Income Support (reg. 8(2)(a)). +# +# Child Benefit in 2026-27: £27.05 a week for the eldest child and £17.90 for +# each other child (gov/hmrc/child_benefit/amount). Housing Benefit and the +# other legacy schemes take their young person from the same definition +# (SSCBA 1992 s.142; HB Regs 2006 reg. 19). + +- name: Case 1, a 16-year-old who has left education is a qualifying young person for the year (reg. 4). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + is_qualifying_young_person_for_child_benefit: [false, true] + is_child_or_qualifying_young_person_for_child_benefit: [false, true] + is_child_benefit_payable_for_person: [false, true] + # 27.05 * 52. + child_benefit_entitlement: 1_406.60 + # The legacy schemes' young person follows Child Benefit. + is_child_or_young_person_for_legacy_benefits: [false, true] + is_lone_parent: true + +- name: Case 2, a 16-year-old apprentice is a qualifying young person under reg. 4, which has no education condition. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + apprentice: + age: 16 + current_education: POST_SECONDARY + is_apprentice: true + weekly_hours: 30 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, apprentice] + output: + is_qualifying_young_person_for_child_benefit: [false, true] + # Reg. 7(3) bars only Child Benefit payable through reg. 7. + is_child_benefit_payable_for_person: [false, true] + child_benefit_entitlement: 1_406.60 + +- name: Case 3, born 15 September 2009, the 31 August after the 16th birthday is in 2026-27 (reg. 4). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 17 + date_of_birth: 20090915 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + is_qualifying_young_person_for_child_benefit: [false, true] + child_benefit_entitlement: 1_406.60 + +- name: Case 4, born 31 August 2009, reg. 4 ended on 1 September 2025, before the year. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 17 + date_of_birth: 20090831 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + is_qualifying_young_person_for_child_benefit: [false, false] + child_benefit_entitlement: 0 + is_child_or_young_person_for_legacy_benefits: [false, false] + is_lone_parent: false + +- name: Case 5, a 17-year-old and an 18-year-old who have left education are not qualifying young persons by default. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + seventeen: + age: 17 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + eighteen: + age: 18 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, seventeen, eighteen] + output: + is_qualifying_young_person_for_child_benefit: [false, false, false] + child_benefit_entitlement: 0 + +- name: Case 6, a 16-year-old receiving a benefit in their own right is not a qualifying young person (reg. 8). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + receives_benefits_in_own_right: true + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + is_qualifying_young_person_for_child_benefit: [false, false] + child_benefit_entitlement: 0 + +- name: Case 7, a 17-year-old in an extension period is a qualifying young person (reg. 5). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 17 + current_education: NOT_IN_EDUCATION + is_in_child_benefit_extension_period: true + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + is_qualifying_young_person_for_child_benefit: [false, true] + child_benefit_entitlement: 1_406.60 + is_lone_parent: true + +- name: Case 8, the extension period needs the young person not to be in remunerative work (reg. 5(2)(c)). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 17 + current_education: NOT_IN_EDUCATION + is_in_child_benefit_extension_period: true + weekly_hours: 24 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + is_in_remunerative_work_for_child_benefit: [false, true] + is_qualifying_young_person_for_child_benefit: [false, false] + child_benefit_entitlement: 0 + +- name: Case 9, the extension period is for under-18s only (reg. 5(1)). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 18 + current_education: NOT_IN_EDUCATION + is_in_child_benefit_extension_period: true + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + is_qualifying_young_person_for_child_benefit: [false, false] + child_benefit_entitlement: 0 + +- name: Case 10, an 18-year-old before the terminal date is a qualifying young person (reg. 7). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 18 + current_education: NOT_IN_EDUCATION + is_before_child_benefit_terminal_date: true + weekly_hours: 23 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + is_in_remunerative_work_for_child_benefit: [false, false] + is_qualifying_young_person_for_child_benefit: [false, true] + is_child_benefit_payable_for_person: [false, true] + child_benefit_entitlement: 1_406.60 + is_lone_parent: true + +- name: Case 11, in remunerative work a young person before the terminal date stays a qualifying young person but brings no Child Benefit (reg. 7(3)). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 18 + current_education: NOT_IN_EDUCATION + is_before_child_benefit_terminal_date: true + weekly_hours: 24 + employment_income: 20_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + would_claim_uc: false + households: + household: + members: [parent, school_leaver] + country: ENGLAND + output: + is_qualifying_young_person_for_child_benefit: [false, true] + is_child_or_qualifying_young_person_for_child_benefit: [false, true] + is_child_benefit_payable_for_person: [false, false] + child_benefit_entitlement: 0 + # Still a young person for Housing Benefit and the other legacy schemes. + is_child_or_young_person_for_legacy_benefits: [false, true] + is_lone_parent: true + # Their £20,000 of earnings is not the parent's (HB Regs 2006 reg 25(3), + # IS Regs 1987 reg 23(2), CTR applicant-and-partner rules; #2387). The + # parent has no income of their own. + housing_benefit_applicable_income: 0 + income_support_applicable_income: 0 + council_tax_reduction_applicable_income: 0 + +- name: Case 12, reg. 7(3) does not stop Child Benefit payable through another condition. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + is_before_child_benefit_terminal_date: true + weekly_hours: 40 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + # Reg. 4 also applies at 16, and has no remunerative work bar. + is_child_benefit_payable_for_person: [false, true] + child_benefit_entitlement: 1_406.60 + +- name: Case 13, a working school leaver before the terminal date does not take the eldest-child rate from a younger child. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 18 + current_education: NOT_IN_EDUCATION + is_before_child_benefit_terminal_date: true + weekly_hours: 30 + is_claimant_or_partner: false + child: + age: 10 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver, child] + output: + child_benefit_child_index: [-1, -1, 1] + # One child at the eldest rate: 27.05 * 52. + child_benefit_entitlement: 1_406.60 + +- name: Case 14, the terminal date condition ends at 20. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 45 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 20 + current_education: NOT_IN_EDUCATION + is_before_child_benefit_terminal_date: true + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + is_qualifying_young_person_for_child_benefit: [false, false] + child_benefit_entitlement: 0 + +- name: Case 15, a 16-year-old at school and a 16-year-old who has left are both qualifying young persons, so two rates. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + at_school: + age: 16 + current_education: UPPER_SECONDARY + is_claimant_or_partner: false + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, at_school, school_leaver] + output: + is_qualifying_young_person_for_child_benefit: [false, true, true] + # (27.05 + 17.90) * 52. + child_benefit_entitlement: 2_337.40 + +- name: Case 16, a lone 16-year-old who has left education is not their own qualifying young person. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: true + benunits: + benunit: + members: [school_leaver] + output: + is_qualifying_young_person_for_child_benefit: true + is_child_or_qualifying_young_person_for_child_benefit: false + child_benefit_entitlement: 0 + +- name: Case 17, a 16-year-old who reports their own income-related ESA is not a qualifying young person (reg. 8(2)(e)). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + esa_income_reported: 5_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + receives_benefits_in_own_right: [false, false] + is_qualifying_young_person_for_child_benefit: [false, false] + is_child_or_young_person_for_legacy_benefits: [false, false] + child_benefit_entitlement: 0 + is_lone_parent: false + +- name: Case 18, a 16-year-old who reports their own income-based JSA is not a qualifying young person (reg. 8(2)(b)). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + jsa_income_reported: 5_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + receives_benefits_in_own_right: [false, false] + is_qualifying_young_person_for_child_benefit: [false, false] + is_child_or_young_person_for_legacy_benefits: [false, false] + child_benefit_entitlement: 0 + is_lone_parent: false + +# Cases 19 to 26: Income Support in the young person's own right (reg. +# 8(2)(a)). receives_income_support_in_own_right reads the person's own +# reported Income Support while DWP pays Income Support +# (gov.dwp.income_support.active) and no reform neutralises income_support. +# A year's period reads that parameter's value on 30 April, so it is true +# for 2025-26 and false from 2026-27 (DWP made the last payments on 31 March +# 2026). +# +# 2018-19 rates: Child Benefit £20.70 a week for the eldest child and £13.70 +# for each other child; PIP daily living standard rate £57.30; Carer's +# Allowance £64.60; Income Support personal allowance for a lone parent aged +# 18 or over £73.10; colour TV licence £150.50. The 2018 cases set the +# cost-of-living payment, which DWP first paid in 2022, to zero. In Cases 19 +# to 22, 24 and 25 a parent aged 40 on PIP daily living lives with a young +# person who has left education and cares for them 35 hours a week. No +# family claims tax credits or Universal Credit, so Child Benefit is the only +# award the young person's status changes. + +- name: Case 19, a 16-year-old school leaver who receives Income Support in their own right is not a qualifying young person (reg. 4, reg. 8(2)(a)). + period: 2018 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + pip_dl_category: STANDARD + carer: + age: 16 + date_of_birth: 20020115 + current_education: NOT_IN_EDUCATION + care_hours: 35 + income_support_reported: 3_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, carer] + would_claim_uc: false + households: + household: + members: [parent, carer] + cost_of_living_support_payment: 0 + output: + # Born 15 January 2002: the reg. 4 period runs to 31 August 2018. + is_before_first_september_following_16th_birthday: [false, true] + is_on_income_support: [false, true] + receives_income_support_in_own_right: [false, true] + is_qualifying_young_person_for_child_benefit: [false, false] + is_child_or_young_person_for_legacy_benefits: [false, false] + child_benefit: 0 + # PIP 57.30 x 52 = 2,979.60; Carer's Allowance 64.60 x 52 = 3,359.20; + # less TV licence 150.50: 6,188.30. The model calculates Income Support + # only for the claimant's family, so the 16-year-old's own 3,000 is in no + # income here. + household_net_income: 6_188.30 + +- name: Case 20, the same family without the 16-year-old's Income Support gets Child Benefit for them. + period: 2018 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + pip_dl_category: STANDARD + carer: + age: 16 + date_of_birth: 20020115 + current_education: NOT_IN_EDUCATION + care_hours: 35 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, carer] + would_claim_uc: false + households: + household: + members: [parent, carer] + cost_of_living_support_payment: 0 + output: + receives_income_support_in_own_right: [false, false] + is_qualifying_young_person_for_child_benefit: [false, true] + # 20.70 x 52. + child_benefit: 1_076.40 + # Case 19's 6,188.30 + 1,076.40 = 7,264.70. + household_net_income: 7_264.70 + +- name: Case 21, a 17-year-old in an extension period who receives Income Support in their own right is not a qualifying young person (reg. 5, reg. 8(2)(a)). + period: 2018 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + pip_dl_category: STANDARD + carer: + age: 17 + date_of_birth: 20010115 + current_education: NOT_IN_EDUCATION + is_in_child_benefit_extension_period: true + care_hours: 35 + income_support_reported: 3_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, carer] + would_claim_uc: false + households: + household: + members: [parent, carer] + cost_of_living_support_payment: 0 + output: + # Born 15 January 2001: the reg. 4 period ended on 31 August 2017. + is_before_first_september_following_16th_birthday: [false, false] + meets_qualifying_young_person_extension_period_condition_for_child_benefit: [false, true] + receives_income_support_in_own_right: [false, true] + is_qualifying_young_person_for_child_benefit: [false, false] + child_benefit: 0 + # As Case 19: 2,979.60 + 3,359.20 - 150.50 = 6,188.30. + household_net_income: 6_188.30 + +- name: Case 22, an 18-year-old before the terminal date who receives Income Support in their own right is not a qualifying young person (reg. 7, reg. 8(2)(a)). + period: 2018 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + pip_dl_category: STANDARD + carer: + age: 18 + date_of_birth: 20000115 + current_education: NOT_IN_EDUCATION + is_before_child_benefit_terminal_date: true + care_hours: 35 + income_support_reported: 3_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, carer] + would_claim_uc: false + households: + household: + members: [parent, carer] + cost_of_living_support_payment: 0 + output: + is_before_first_september_following_16th_birthday: [false, false] + receives_income_support_in_own_right: [false, true] + is_qualifying_young_person_for_child_benefit: [false, false] + child_benefit: 0 + # As Case 19: 2,979.60 + 3,359.20 - 150.50 = 6,188.30. + household_net_income: 6_188.30 + +- name: Case 23, a 17-year-old at school who receives Income Support in their own right is not a qualifying young person (reg. 3, reg. 8(2)(a)). + period: 2018 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + pip_dl_category: STANDARD + student: + age: 17 + date_of_birth: 20010115 + current_education: UPPER_SECONDARY + income_support_reported: 3_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, student] + would_claim_uc: false + households: + household: + members: [parent, student] + cost_of_living_support_payment: 0 + output: + # Full-time non-advanced education begun before 19 (reg. 3). + meets_qualifying_young_person_education_or_training_condition_for_child_benefit: [false, true] + meets_qualifying_young_person_entry_condition_for_child_benefit: [false, true] + receives_income_support_in_own_right: [false, true] + is_qualifying_young_person_for_child_benefit: [false, false] + child_benefit: 0 + # PIP 57.30 x 52 = 2,979.60, less TV licence 150.50: 2,829.10. + household_net_income: 2_829.10 + +- name: Case 24, under a reform that neutralises income_support, Case 19's report does not end the status. + period: 2018 + absolute_error_margin: 0.01 + reforms: + - policyengine_uk.tests.income_support_reforms.neutralise_income_support + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + pip_dl_category: STANDARD + carer: + age: 16 + date_of_birth: 20020115 + current_education: NOT_IN_EDUCATION + care_hours: 35 + income_support_reported: 3_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, carer] + would_claim_uc: false + households: + household: + members: [parent, carer] + cost_of_living_support_payment: 0 + output: + is_on_income_support: [false, false] + receives_income_support_in_own_right: [false, false] + is_qualifying_young_person_for_child_benefit: [false, true] + # 20.70 x 52. + child_benefit: 1_076.40 + # As Case 20: 6,188.30 + 1,076.40 = 7,264.70. + household_net_income: 7_264.70 + +- name: Case 25, in 2026-27, after DWP stopped paying Income Support, a report of it does not end the status. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + pip_dl_category: STANDARD + carer: + age: 16 + date_of_birth: 20100115 + current_education: NOT_IN_EDUCATION + care_hours: 35 + income_support_reported: 3_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, carer] + would_claim_uc: false + households: + household: + members: [parent, carer] + output: + # Born 15 January 2010: the reg. 4 period runs to 31 August 2026. + is_before_first_september_following_16th_birthday: [false, true] + receives_income_support_in_own_right: [false, false] + is_qualifying_young_person_for_child_benefit: [false, true] + # 27.05 x 52. + child_benefit: 1_406.60 + # PIP 76.70 x 52 = 3,988.40; Carer's Allowance 86.45 x 52 = 4,495.40; + # Child Benefit 1,406.60; Household Energy Payment 100; less TV licence + # 180: 9,810.40. + household_net_income: 9_810.40 + +- name: Case 26, a 16-year-old school leaver whose lone parent claims Income Support for the family, and who has none of their own, stays a qualifying young person. + period: 2018 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + income_support_reported: 3_000 + child: + age: 4 + is_claimant_or_partner: false + school_leaver: + age: 16 + date_of_birth: 20020115 + current_education: NOT_IN_EDUCATION + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, child, school_leaver] + would_claim_uc: false + # The family takes up no free childcare, so the 4-year-old's + # universal hours add nothing to household income. + would_claim_universal_childcare: false + households: + household: + members: [parent, child, school_leaver] + cost_of_living_support_payment: 0 + output: + is_on_income_support: [true, false, false] + receives_income_support_in_own_right: [true, false, false] + is_qualifying_young_person_for_child_benefit: [false, false, true] + is_child_or_young_person_for_legacy_benefits: [false, true, true] + # A lone parent of a child under 5 with no income: the personal + # allowance, 73.10 x 52 = 3,801.20. + income_support: 3_801.20 + # (20.70 + 13.70) x 52 = 1,788.80. + child_benefit: 1_788.80 + # 3,801.20 + 1,788.80, less TV licence 150.50: 5,439.50. + household_net_income: 5_439.50 + +- name: Case 27, a 17-year-old at school who reports their own contributory ESA is not a qualifying young person (reg. 3 with reg. 8(2)(e)). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + student: + age: 17 + current_education: UPPER_SECONDARY + esa_contrib_reported: 5_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, student] + output: + is_qualifying_young_person_for_child_benefit: [false, false] + child_benefit_entitlement: 0 + +- name: Case 28, a 16-year-old school leaver who reports contribution-based JSA stays a qualifying young person (reg. 8(2)(b) lists income-based JSA only). + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 40 + is_parent: true + is_claimant_or_partner: true + school_leaver: + age: 16 + current_education: NOT_IN_EDUCATION + jsa_contrib_reported: 5_000 + is_claimant_or_partner: false + benunits: + benunit: + members: [parent, school_leaver] + output: + is_qualifying_young_person_for_child_benefit: [false, true] + # 27.05 x 52 = 1,406.60. + child_benefit_entitlement: 1_406.60 diff --git a/policyengine_uk/tests/test_benefit_cap_specified_benefits_properties.py b/policyengine_uk/tests/test_benefit_cap_specified_benefits_properties.py index 076d8ffa2c..5872996bd9 100644 --- a/policyengine_uk/tests/test_benefit_cap_specified_benefits_properties.py +++ b/policyengine_uk/tests/test_benefit_cap_specified_benefits_properties.py @@ -32,8 +32,10 @@ qualifying young person"; HB reg 75F(1)(e) names a "child or young person". UC includes a 16-year-old school leaver (reg 5(1)(a)) who does not receive benefits in their own right (reg 5(5)) and is not looked after (reg 4(6)). -HB young persons follow Child Benefit (reg 19), so only the education-based -young-person role counts for Housing Benefit below. UC reg 83(1)(c) names +HB young persons follow Child Benefit (reg 19), which includes a 16-year-old +school leaver until the 31 August after their 16th birthday (Child Benefit +(General) Regulations 2006 reg 4), counted for the year as the model does, +unless they receive benefits in their own right (reg 8). UC reg 83(1)(c) names "a claimant" for attendance allowance, which includes armed forces independence payment (reg 2); HB reg 75F(1)(ea) also names "a young person" for it. Only UC reg 83(1)(a), (j) names the LCWRA and carer elements; only @@ -282,25 +284,40 @@ def own_benefits(m): return any(m.get(report, 0) > 0 for report in OWN_RIGHT_REPORTS) +def child_benefit_own_benefits(m): + # Child Benefit (General) Regulations 2006 reg 8(2)(b), (e), (f). + return any( + m.get(report, 0) > 0 + for report in OWN_RIGHT_REPORTS + if report != "jsa_contrib_reported" + ) + + def reference_specified_benefit(members, benunit_inputs, scheme): """UC Regs 2013 reg 83(1) or HB Regs 2006 reg 75F(1), read directly, with roles known by construction.""" claimants = [m for m in members if m["_role"] == "claimant"] # UC reg 83 names a "qualifying young person" (reg 5), which includes a # 16-year-old school leaver (reg 5(1)(a)); HB reg 75F names a "young - # person" (reg 19), a Child Benefit qualifying young person. The - # school-leaver role here has no Child Benefit education or terminal-date - # circumstance, so it is not one. - young_roles = ("young_person", "sixteen") if scheme == "uc" else ("young_person",) + # person" (reg 19), a Child Benefit qualifying young person, which + # includes one too (Child Benefit (General) Regulations 2006 reg 4). + # Neither counts one receiving benefits in their own right. + young_roles = ("young_person", "sixteen") # The UC predicate supplements its qualifying-young-person test with a # separate age-16 approximation, which reads only the explicit own-right - # flag. Reported awards remove older UC young persons, but do not change - # that approximation or the Housing Benefit test. + # flag, so reported awards remove only older UC young persons. The Child + # Benefit test, which Housing Benefit follows, removes anyone who reports + # their own Universal Credit, ESA or income-based JSA (Child Benefit + # (General) Regulations 2006 reg 8(2)(b), (e), (f)). young_persons = [ m for m in members if m["_role"] in young_roles - and (scheme != "uc" or m["age"] == 16 or not own_benefits(m)) + and ( + (m["age"] == 16 or not own_benefits(m)) + if scheme == "uc" + else not child_benefit_own_benefits(m) + ) ] children = [m for m in members if m["_role"] == "child"] # No capital, so the couple's income-related award is their reports, @@ -406,18 +423,17 @@ def reference_cap(members, scheme): # Each scheme's rate: a single claimant (UC reg 80A(2)(c)) or a claimant # who neither has a partner nor is a lone parent (HB regs 2(1), 75CA(2)), # with no child or young person. The family-rate helper counts any - # 16-year-old for the year (UC reg 5(1)(a); for HB, Child Benefit's - # school-leaver terminal dates, which annual ages cannot place); it does + # 16-year-old for the year (UC reg 5(1)(a); for HB, Child Benefit reg 4 + # and its school-leaver terminal dates, which annual ages cannot place); it does # not yet apply reg 5(5) to one receiving benefits in their own right. - # Older UC young persons cease to count when they report their own award; - # Housing Benefit continues to read the existing Child Benefit test. + # Older young persons cease to count when they report their own award: + # for UC any award (reg 5(5)), for Housing Benefit the awards the Child + # Benefit test reads (Child Benefit (General) Regulations 2006 reg 8). claimants = sum(m["_role"] == "claimant" for m in members) + reports = own_benefits if scheme == "uc" else child_benefit_own_benefits dependants = any( m["_role"] in ("child", "sixteen", "sixteen_own_right") - or ( - m["_role"] == "young_person" - and (scheme != "uc" or m["age"] == 16 or not own_benefits(m)) - ) + or (m["_role"] == "young_person" and (m["age"] == 16 or not reports(m))) for m in members ) return SINGLE_CAP if claimants == 1 and not dependants else FAMILY_CAP @@ -615,8 +631,8 @@ def test_each_head_alone_matches_regulations(mode): assert not mismatches, mismatches # Both outcomes occur under each scheme. assert exempt.any() and not exempt.all() - # The schemes differ: UC's elements and 16-year-old school leaver, HB's - # young person's armed forces independence payment. + # The schemes differ: UC's elements, HB's young person's armed forces + # independence payment. assert (exemptions["uc"] != exemptions["hb"]).any() # Every role meets every circumstance. assert len(units) == len(SINGLE_HEAD_ROLES) * len(CIRCUMSTANCES) 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 d7df438ec9..95f767cdcb 100644 --- a/policyengine_uk/tests/test_child_and_adult_definitions_properties.py +++ b/policyengine_uk/tests/test_child_and_adult_definitions_properties.py @@ -50,7 +50,9 @@ 19 and without own-right benefits, that route alone makes a UC or PC qualifying young person whatever the education inputs; without it the education route is unchanged. Own-right benefits exclude both routes. UC - and PC agree below 19, where only UC's terminal date differs. + and PC agree below 19, where only UC's terminal date differs. Child + Benefit reg 4 and CTC reg 4 (until the 31 August after the 16th birthday) + count for the same years. 6. Supplied claimant/partner role vectors, valid or not, are returned unchanged on any benefit unit. Widening an unflagged member's gap below the claimant, with the claimant, flags, other members and HBAI adulthood @@ -61,15 +63,20 @@ Scope: these are tests of the ENCODED statutory limbs, not full entitlement. Education status proxies full-time non-advanced study: hours, employment -contracts, approved providers and interruptions are unobserved. CB/CTC leaver -extensions are omitted. UC/PC's route to the 1 September after the 16th -birthday, which has no education condition, counts for a year that starts -before that date (invariant 5). The tests here set UC's terminal-date input, -false unless a test varies it; PC's terminal date follows the 19th-birthday -date fact, which is true for everyone under 20 when only an age is given -(test_qualifying_young_person_terminal_date_properties.py covers both dates). -Nobody here reports Income Support, which SPC reg 4A(5) lists and UC reg 5(5) -does not. +contracts, approved providers and interruptions are unobserved. Child +Benefit's extension period and terminal date (regs 5 and 7) and CTC's 20 +weeks after leaving (reg 5(3)(b)) are inputs, false here, with their own +tests in test_child_benefit_school_leaver_routes_properties.py and +test_child_tax_credit_school_leaver_routes_properties.py. The route to the 1 +September after the 16th birthday (UC, PC) or the 31 August before it (Child +Benefit reg 4, CTC reg 4), which has no education condition, counts for a +year that starts before that date (invariant 5). Nobody here is in +remunerative work, which ends CTC's route. The tests here set UC's +terminal-date input, false unless a test varies it; PC's terminal date +follows the 19th-birthday date fact, which is true for everyone under 20 when +only an age is given (test_qualifying_young_person_terminal_date_properties.py +covers both dates). Nobody here reports Income Support, which SPC reg 4A(5) +lists and UC reg 5(5) does not. Own-benefit receipt collapses different programme-specific benefit lists. UC/CTC/PC blanket looked-after exclusions omit statutory exceptions. HBAI's fallback assumes dependence at 16--17 regardless of study, requires an @@ -205,29 +212,33 @@ def statutory_child(age): def in_route_to_first_september_after_16th_birthday(p): - # UC reg 5(1)(a) and SPC reg 4A(1)(a) have no education condition. With no - # date of birth, a whole age on 6 October places the birthday on 6 April, - # so the year (from 6 April) starts before the 1 September following the - # 16th birthday exactly when that age is under 17. + # UC reg 5(1)(a), SPC reg 4A(1)(a) and Child Benefit reg 4 have no + # education condition. With no date of birth, a whole age on 6 October + # places the birthday on 6 April, so the year (from 6 April) starts before + # the 1 September following the 16th birthday exactly when that age is + # under 17. Child Benefit's 31 August is the day before. return p.get("is_before_first_september_following_16th_birthday", p["age"] < 17) def qualifying_young_person(p, programme): - # CB and CTC: education/training routes only (see omissions in module - # docstring). UC and PC: also the route with no education condition. + # Every programme: the education/training route, and the route with no + # education condition. # CB: https://www.legislation.gov.uk/uksi/2006/223/regulation/3 + # CB at 16: https://www.legislation.gov.uk/uksi/2006/223/regulation/4 + # CTC at 16: https://www.legislation.gov.uk/uksi/2002/2007/regulation/4 # CB own-benefit restriction: https://www.legislation.gov.uk/uksi/2006/223/regulation/8 # UC: https://www.legislation.gov.uk/uksi/2013/376/regulation/5 # CTC: https://www.legislation.gov.uk/uksi/2002/2007/regulation/5 # PC: https://www.legislation.gov.uk/uksi/2002/1792/regulation/4A if not 16 <= p["age"] < 20 or p["receives_benefits_in_own_right"]: return False - if programme in { - "universal_credit", - "pension_credit", - } and in_route_to_first_september_after_16th_birthday(p): - return True - if not (non_advanced_education(p) or p["is_in_approved_training"]): + studying = non_advanced_education(p) or p["is_in_approved_training"] + if in_route_to_first_september_after_16th_birthday(p): + # CTC reg 4 lifts only the first condition, for a person who has left + # education; one still in it is tested as a student below. + if programme != "child_tax_credit" or not studying: + return True + if not studying: return False if ( p["age"] >= 19 diff --git a/policyengine_uk/tests/test_child_benefit_school_leaver_routes_properties.py b/policyengine_uk/tests/test_child_benefit_school_leaver_routes_properties.py new file mode 100644 index 0000000000..6d4dcc2747 --- /dev/null +++ b/policyengine_uk/tests/test_child_benefit_school_leaver_routes_properties.py @@ -0,0 +1,433 @@ +"""Child Benefit qualifying young persons who have left education. + +Child Benefit (General) Regulations 2006: a person aged 16 to 19 is a +qualifying young person while any of regs 3 to 7 applies (reg. 2(2)) and +they are not receiving a benefit in their own right (reg. 8). + +- Reg. 3: non-advanced education or approved training, begun before 19. +- Reg. 4: under 17 and left education, until the 31 August next following + the 16th birthday (the 1 September for a 16th birthday on 31 August). +- Reg. 5: under 18, in an extension period, not in remunerative work. +- Reg. 7: until the terminal date after leaving; reg. 7(3) pays no Child + Benefit "by virtue of this regulation" in a week of remunerative work (24 + hours or more, reg. 1(3)). + +Invariants: + +1. Reg. 4, against calendar arithmetic that shares no code with the model: + a person outside education is a qualifying young person for a year + exactly when they are 16 to 19 on 6 October and the year starts (6 April) + on or before the reg. 4 end date, for every date of birth. +2. Over every combination of age, education, training, own-right benefits, + the three leaver facts and hours: the status equals the union of the four + conditions within ages 16 to 19 and without own-right benefits. So each + condition ends at its age limit, own-right benefits exclude every + condition, and with the three leaver facts off the status is reg. 3's + alone, as before. +3. Switching a leaver fact on never removes the status. +4. Child Benefit is payable for the benefit unit's child or qualifying young + person unless they qualify through reg. 7 alone and are in remunerative + work. They stay a qualifying young person, and a legacy young person. +5. Child Benefit entitlement is the eldest payable member at the eldest + rate and each other at the additional rate, for 52 weeks. +6. Reg. 8(2)(a): in a year when DWP pays Income Support, a person who + reports Income Support of their own is never a qualifying young person, + on any route, whoever else in the family claims it. +7. Without a report, setting receives_income_support_in_own_right false as + an input leaves the status unchanged; with it set false, a report + changes nothing, so the status is the one without the report. +""" + +from datetime import date, timedelta +from itertools import product +from unittest.mock import patch + +import numpy as np +from hypothesis import HealthCheck, assume, 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, +) + +YEAR = 2026 +_PROPERTY_SYSTEM = system.clone() +EDUCATIONS = tuple(item.name for item in EducationType) +ROUTE = "is_before_first_september_following_16th_birthday" +EXTENSION = "is_in_child_benefit_extension_period" +TERMINAL = "is_before_child_benefit_terminal_date" +QYP = "is_qualifying_young_person_for_child_benefit" +SETTINGS = settings( + max_examples=25, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +def person(age, **inputs): + return { + "age": age, + "current_education": "NOT_IN_EDUCATION", + "is_in_approved_training": False, + "is_apprentice": False, + "receives_benefits_in_own_right": False, + "is_looked_after_by_local_authority": False, + "age_started_or_accepted_current_education_or_training": 17, + "weekly_hours": 0, + EXTENSION: False, + TERMINAL: False, + "is_claimant_or_partner": False, + **inputs, + } + + +def simulate(units, year=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) + variables = people[0].keys() + assert all(p.keys() == variables for p in people) + for variable in variables: + sim.set_input(variable, year, [p[variable] for p in people]) + sim.input_variables = list(variables) + sim.reset_calculations() + return sim + + +def regulation_4_end_date(born): + # Reg. 4(2): "the 31st August next following the person's 16th birthday"; + # reg. 4(3): for a 16th birthday on 31 August, "the 1st September + # immediately following". A 29 February birthday is before 31 August + # whichever day it is kept on in other years. + # https://www.legislation.gov.uk/uksi/2006/223/regulation/4 + if (born.month, born.day) == (8, 31): + return date(born.year + 16, 9, 1) + return date(born.year + 16 + ((born.month, born.day) > (8, 31)), 8, 31) + + +def age_on_6_october(born, year): + return year - born.year - ((10, 6) < (born.month, born.day)) + + +def assert_regulation_4_matches_calendar(births, year): + units = [ + [ + person(40, is_claimant_or_partner=True, date_of_birth=0), + person(age_on_6_october(born, year), date_of_birth=int(f"{born:%Y%m%d}")), + ] + for born in births + ] + # "Has not passed": the year, from 6 April, reaches the period if it + # starts on or before the end date. + expected = [ + 16 <= age_on_6_october(born, year) < 20 + and date(year, 4, 6) <= regulation_4_end_date(born) + for born in births + ] + actual = simulate(units, year).calculate(QYP, year)[1::2] + np.testing.assert_array_equal(actual, expected) + return expected + + +def test_regulation_4_for_every_date_of_birth(): + # Every date of birth from 2006 to 2011: ages 14 to 20 on 6 October 2026, + # with the 31 August and 1 September edges in each year. + start, end = date(2006, 1, 1), date(2012, 1, 1) + births = [start + timedelta(days=d) for d in range((end - start).days)] + expected = assert_regulation_4_matches_calendar(births, YEAR) + # Not vacuous, and the edges fall where the regulation puts them. + by_birth = dict(zip(births, expected)) + assert by_birth[date(2010, 6, 1)] and not by_birth[date(2009, 6, 1)] + # 16 on 31 August 2025: the period ended on 1 September 2025. + assert not by_birth[date(2009, 8, 31)] + # 16 on 1 September 2025: the period runs to 31 August 2026. + assert by_birth[date(2009, 9, 1)] + # 16 on 6 October 2026 is 16 for the year; 16 on 7 October is still 15. + assert by_birth[date(2010, 10, 6)] and not by_birth[date(2010, 10, 7)] + + +@SETTINGS +@given( + st.lists(st.dates(date(1990, 1, 1), date(2031, 12, 31)), min_size=1, max_size=30), + st.integers(2013, 2031), +) +def test_regulation_4_matches_calendar_arithmetic_in_any_year(births, year): + births = [born for born in births if age_on_6_october(born, year) >= 0] + assume(births) + assert_regulation_4_matches_calendar(births, year) + + +def regulation_3(p): + # Education status stands in for full-time non-advanced education. + # https://www.legislation.gov.uk/uksi/2006/223/regulation/3 + studying = ( + p["current_education"] not in {"NOT_IN_EDUCATION", "TERTIARY"} + and not p["is_apprentice"] + ) or p["is_in_approved_training"] + begun_before_19 = ( + p["age"] < 19 or p["age_started_or_accepted_current_education_or_training"] < 19 + ) + return studying and begun_before_19 + + +def remunerative_work(p): + # https://www.legislation.gov.uk/uksi/2006/223/regulation/1 + return p["weekly_hours"] >= 24 + + +def regulation_5(p): + # https://www.legislation.gov.uk/uksi/2006/223/regulation/5 + return p["age"] < 18 and p[EXTENSION] and not remunerative_work(p) + + +def qualifying_young_person(p): + if not 16 <= p["age"] < 20 or p["receives_benefits_in_own_right"]: + return False + return regulation_3(p) or p[ROUTE] or regulation_5(p) or p[TERMINAL] + + +def payable(p): + # Reg. 7(3) bars Child Benefit payable through reg. 7 only. + if p["is_claimant_or_partner"]: + return False + if p["age"] < 16: + return True + if not qualifying_young_person(p): + return False + through_another = regulation_3(p) or p[ROUTE] or regulation_5(p) + return through_another or not remunerative_work(p) + + +GRID = [ + person( + age, + current_education=education, + is_in_approved_training=training, + receives_benefits_in_own_right=own_right, + weekly_hours=hours, + **{ROUTE: route, EXTENSION: extension, TERMINAL: terminal}, + ) + for age, education, training, own_right, hours in product( + range(14, 22), EDUCATIONS, (False, True), (False, True), (0, 23, 24) + ) + for route, extension, terminal in product((False, True), repeat=3) +] + + +def test_status_is_the_union_of_the_four_conditions(): + # 8 ages * every education value * 2 * 2 * 3 hours, each with the 8 + # combinations of the three leaver facts (adjacent rows). + units = [ + [person(45, is_claimant_or_partner=True, **{ROUTE: False}), p] for p in GRID + ] + sim = simulate(units) + status = sim.calculate(QYP, YEAR)[1::2] + # Invariant 2. + np.testing.assert_array_equal(status, [qualifying_young_person(p) for p in GRID]) + age = np.array([p["age"] for p in GRID]) + assert not status[(age < 16) | (age >= 20)].any() + assert not status[ + np.array([p["receives_benefits_in_own_right"] for p in GRID]) + ].any() + assert status.any() + # With the leaver facts off, reg. 3 alone, as before. + off = np.array([not (p[ROUTE] or p[EXTENSION] or p[TERMINAL]) for p in GRID]) + np.testing.assert_array_equal( + status[off], + [ + 16 <= p["age"] < 20 + and not p["receives_benefits_in_own_right"] + and regulation_3(p) + for p, o in zip(GRID, off) + if o + ], + ) + # Invariant 3: within each block of 8, a row with more facts on never + # loses the status. Rows are ordered (route, extension, terminal). + blocks = status.reshape(-1, 8) + for less, more in ( + (0, 1), + (0, 2), + (0, 4), + (1, 3), + (1, 5), + (2, 3), + (2, 6), + (4, 5), + (4, 6), + (3, 7), + (5, 7), + (6, 7), + ): + assert (blocks[:, more] >= blocks[:, less]).all(), (less, more) + # Invariant 4. + np.testing.assert_array_equal( + sim.calculate("is_child_benefit_payable_for_person", YEAR)[1::2], + [payable(p) for p in GRID], + ) + family = sim.calculate( + "is_child_or_qualifying_young_person_for_child_benefit", YEAR + )[1::2] + legacy = sim.calculate("is_child_or_young_person_for_legacy_benefits", YEAR)[1::2] + np.testing.assert_array_equal(family, (age < 16) | status) + np.testing.assert_array_equal(legacy, family) + barred = family & ~np.array([payable(p) for p in GRID]) + # Not vacuous: some young persons are barred, all of them 16 or over, + # before the terminal date and working 24 hours. + assert barred.any() + assert all( + p[TERMINAL] and p["weekly_hours"] == 24 and p["age"] >= 16 + for p, b in zip(GRID, barred) + if b + ) + + +member = st.builds( + person, + st.integers(0, 21), + current_education=st.sampled_from(EDUCATIONS), + is_in_approved_training=st.booleans(), + receives_benefits_in_own_right=st.booleans(), + weekly_hours=st.sampled_from([0, 10, 23, 24, 40]), + **{ROUTE: st.booleans(), EXTENSION: st.booleans(), TERMINAL: st.booleans()}, +) + + +@st.composite +def benefit_units(draw): + 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] + others + + +@SETTINGS +@given(st.lists(benefit_units(), min_size=1, max_size=6), st.sampled_from([2024, 2026])) +def test_child_benefit_entitlement_counts_payable_members(units, year): + # Invariant 5, from the inputs and the weekly rates alone. + sim = simulate(units, year) + rates = sim.tax_benefit_system.parameters(f"{year}-06-01").gov.hmrc.child_benefit + entitlement = sim.calculate("child_benefit_entitlement", year) + for unit, actual in zip(units, entitlement): + number = sum(payable(p) for p in unit) + weekly = ( + 0 + if number == 0 + else rates.amount.eldest + (number - 1) * rates.amount.additional + ) + np.testing.assert_allclose(actual, weekly * 52, rtol=1e-6) + + +# --- Invariants 6 and 7: Income Support in the young person's own right --- + +INCOME_SUPPORT = "receives_income_support_in_own_right" +OWN_REPORTS = ( + "universal_credit_reported", + "esa_income_reported", + "esa_contrib_reported", + "jsa_income_reported", + "jsa_contrib_reported", +) + + +def family_member(age, **inputs): + defaults = { + "income_support_reported": 0, + "care_hours": 0, + ROUTE: False, + **{report: 0 for report in OWN_REPORTS}, + } + return person(age, **{**defaults, **inputs}) + + +young_or_child = st.builds( + family_member, + st.integers(0, 21), + current_education=st.sampled_from(EDUCATIONS), + is_in_approved_training=st.booleans(), + is_apprentice=st.booleans(), + receives_benefits_in_own_right=st.sampled_from([False, False, False, True]), + weekly_hours=st.sampled_from([0, 23, 24, 40]), + care_hours=st.sampled_from([0, 35]), + income_support_reported=st.sampled_from([0, 0, 1, 3_000]), + **{ROUTE: st.booleans(), EXTENSION: st.booleans(), TERMINAL: st.booleans()}, + **{report: st.sampled_from([0, 0, 0, 2_000]) for report in OWN_REPORTS}, +) +# A parent who can claim Income Support for the family: a carer, or a lone +# parent of a child under 5 when the draw gives them one. +parent = st.builds( + family_member, + st.integers(25, 60), + is_claimant_or_partner=st.just(True), + care_hours=st.sampled_from([0, 35]), + income_support_reported=st.sampled_from([0, 3_000]), +) + + +@st.composite +def families(draw): + if draw(st.booleans()): + parents = draw(st.lists(parent, min_size=1, max_size=2)) + return parents + draw(st.lists(young_or_child, min_size=1, max_size=4)) + # A young person who claims for their own benefit unit. + young = draw(young_or_child.filter(lambda p: p["age"] >= 16)) + others = draw(st.lists(young_or_child, max_size=2)) + return [dict(young, is_claimant_or_partner=True)] + others + + +def case_19_family(): + # child_benefit_school_leaver_routes.yaml Case 19: a 16-year-old school + # leaver, in the reg. 4 period, who cares for a parent and reports their + # own Income Support. + return [ + family_member(40, is_claimant_or_partner=True), + family_member( + 16, care_hours=35, income_support_reported=3_000, **{ROUTE: True} + ), + ] + + +@SETTINGS +@given( + st.lists(families(), min_size=1, max_size=6), st.sampled_from([2016, 2019, 2024]) +) +def test_own_income_support_ends_the_status_on_every_route(units, year): + # Every draw also holds Case 19's family, so there is a status to lose. + units = units + [case_19_family()] + reported = np.array( + [p["income_support_reported"] > 0 for unit in units for p in unit] + ) + sim = simulate(units, year) + # The family's Income Support reads this test through the legacy young + # person; calculating it first shows the test does not read it back. + sim.calculate("income_support", year) + status = sim.calculate(QYP, year) + # Invariant 6, in 2016, 2019 and 2024, when DWP paid Income Support. + assert not status[reported].any() + released = simulate( + [[dict(p, **{INCOME_SUPPORT: False}) for p in unit] for unit in units], year + ).calculate(QYP, year) + unreported = simulate( + [[dict(p, income_support_reported=0) for p in unit] for unit in units], year + ).calculate(QYP, year) + # Invariant 7. + np.testing.assert_array_equal(released[~reported], status[~reported]) + np.testing.assert_array_equal(released, unreported) + # Case 19's 16-year-old has the status without the report, and loses it + # to the report alone. + assert released[-1] and not status[-1] diff --git a/policyengine_uk/tests/test_child_tax_credit_school_leaver_routes_properties.py b/policyengine_uk/tests/test_child_tax_credit_school_leaver_routes_properties.py new file mode 100644 index 0000000000..5796d5c8e0 --- /dev/null +++ b/policyengine_uk/tests/test_child_tax_credit_school_leaver_routes_properties.py @@ -0,0 +1,430 @@ +"""Child Tax Credit qualifying young persons who have left education. + +Child Tax Credit Regulations 2002: + +- Reg. 5(3)(a), (ab), (3A): the first condition is full-time non-advanced + education or approved training, begun before 19. +- Reg. 4(1)-(2): from the 16th birthday until the 31 August that next + follows, the first condition is not required. Reg. 4(1A): a 16th birthday + on 31 August gets no such period. +- Reg. 5(3)(b): under 18, the 20 weeks after leaving while registered for + work or training satisfy the first condition. +- Reg. 5(4), the second condition, always applies: a person who has ceased + full-time education or approved training must not be in remunerative work + (24 hours a week or more, reg. 2(1)), and no one may be receiving a benefit + in their own right. Full-time education at any level counts (reg. 5(5)), so + an advanced student has not ceased, nor has an apprentice still on a + course: the employment exclusion belongs to the first condition. + +Invariants: + +1. Reg. 4, against calendar arithmetic that shares no code with the model: + a person outside education, not working, is a qualifying young person for + a year exactly when they are 16 to 19 on 6 October and some of the year, + from 6 April, falls on or before the last day they are a child or in the + reg. 4 period. For a 16th birthday on 31 August that day is 30 August. +2. Over every combination of age, education, training, apprenticeship, + own-right benefits, the age the course began, the two leaver facts and + hours: the status is the education condition; or the reg. 4 period, + unless the person has ceased education or training and is in remunerative + work; or the 20 weeks for a person under 18 who has ceased and is not in + remunerative work; within ages 16 to 19 and without own-right benefits. + So remunerative work ends both routes for a person who has ceased and + never a student's status, an advanced student keeps the reg. 4 period + whatever their hours, the 20 weeks end at 18 and never reach a student + whose course began at 19, and with the leaver facts off the status is the + education condition's alone, as before. +3. Switching a leaver fact on never removes the status. +4. The benefit unit's child or qualifying young person is a child or + qualifying young person who is not its claimant or partner. +5. Reg. 5(4)(c), which reg. 4(2) keeps for the reg. 4 period: a member who + is not the claimant or partner and reports Income Support of their own + while it is in payment is never a qualifying young person, on any route; + once Income Support has stopped paying (from April 2026) or a reform + neutralises it, the report does not change the status. +""" + +from datetime import date, timedelta +from itertools import product +from unittest.mock import patch + +import numpy as np +from hypothesis import HealthCheck, assume, 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, +) + +YEAR = 2024 +_PROPERTY_SYSTEM = system.clone() +EDUCATIONS = tuple(item.name for item in EducationType) +ROUTE = "is_before_first_september_following_16th_birthday" +EXTENSION = "is_in_child_tax_credit_extension_period" +QYP = "is_qualifying_young_person_for_child_tax_credit" +SETTINGS = settings( + max_examples=25, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) + + +def person(age, **inputs): + return { + "age": age, + "current_education": "NOT_IN_EDUCATION", + "is_in_approved_training": False, + "is_apprentice": False, + "receives_benefits_in_own_right": False, + "is_looked_after_by_local_authority": False, + "age_started_or_accepted_current_education_or_training": 17, + "weekly_hours": 0, + EXTENSION: False, + "is_claimant_or_partner": False, + "income_support_reported": 0, + **inputs, + } + + +def simulate(units, year=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) + variables = people[0].keys() + assert all(p.keys() == variables for p in people) + for variable in variables: + sim.set_input(variable, year, [p[variable] for p in people]) + sim.input_variables = list(variables) + sim.reset_calculations() + return sim + + +def last_day_as_child_or_in_regulation_4_period(born): + # Reg. 4(1): "until 31st August which next follows" the 16th birthday. + # Reg. 4(1A): a 16th birthday on 31 August starts no such period, so the + # last day covered is the day before it, the last day under 16. + # https://www.legislation.gov.uk/uksi/2002/2007/regulation/4 + if (born.month, born.day) == (8, 31): + return date(born.year + 16, 8, 30) + return date(born.year + 16 + ((born.month, born.day) > (8, 31)), 8, 31) + + +def age_on_6_october(born, year): + return year - born.year - ((10, 6) < (born.month, born.day)) + + +def assert_regulation_4_matches_calendar(births, year): + units = [ + [ + person(40, is_claimant_or_partner=True, date_of_birth=0), + person(age_on_6_october(born, year), date_of_birth=int(f"{born:%Y%m%d}")), + ] + for born in births + ] + expected = [ + 16 <= age_on_6_october(born, year) < 20 + and date(year, 4, 6) <= last_day_as_child_or_in_regulation_4_period(born) + for born in births + ] + actual = simulate(units, year).calculate(QYP, year)[1::2] + np.testing.assert_array_equal(actual, expected) + return expected + + +def test_regulation_4_for_every_date_of_birth(): + # Every date of birth from 2004 to 2009: ages 14 to 20 on 6 October 2024. + start, end = date(2004, 1, 1), date(2010, 1, 1) + births = [start + timedelta(days=d) for d in range((end - start).days)] + expected = assert_regulation_4_matches_calendar(births, YEAR) + by_birth = dict(zip(births, expected)) + assert by_birth[date(2008, 6, 1)] and not by_birth[date(2007, 6, 1)] + # 16 on 31 August 2023: reg. 4(1A) gives no period to 31 August 2024. + assert not by_birth[date(2007, 8, 31)] + # 16 on 31 August 2024: a child until 30 August 2024, in the year. + assert by_birth[date(2008, 8, 31)] + # 16 on 1 September 2023: the period runs to 31 August 2024. + assert by_birth[date(2007, 9, 1)] + # 16 on 6 October 2024 is 16 for the year; 16 on 7 October is still 15. + assert by_birth[date(2008, 10, 6)] and not by_birth[date(2008, 10, 7)] + + +@SETTINGS +@given( + st.lists(st.dates(date(1990, 1, 1), date(2031, 12, 31)), min_size=1, max_size=30), + # From 2015: the status reads is_on_income_support, and Income Support + # reads Pension Credit's mixed-age couple date, which has no value + # before 2015 (child_tax_credit fails there already). + st.integers(2015, 2031), +) +def test_regulation_4_matches_calendar_arithmetic_in_any_year(births, year): + births = [born for born in births if age_on_6_october(born, year) >= 0] + assume(births) + assert_regulation_4_matches_calendar(births, year) + + +INCOME_SUPPORT_IN_PAYMENT = {2018: True, 2024: True, 2026: False} + + +def qualifying_young_person_given_income_support(p, year): + # Reg. 5(4)(c): a member who is not the claimant or partner receives + # Income Support on their own report while Income Support is paid. + own_income_support = ( + INCOME_SUPPORT_IN_PAYMENT[year] + and not p["is_claimant_or_partner"] + and p["income_support_reported"] > 0 + ) + return qualifying_young_person(p) and not own_income_support + + +def test_income_support_ends_every_route(): + # Invariant 5 on every grid row that qualifies without Income Support. + qualifying = [p for p in GRID if qualifying_young_person(p)] + reporting = [dict(p, income_support_reported=3_000) for p in qualifying] + routes = { + "education": [first_condition_through_education(p) for p in qualifying], + "regulation 4": [ + p[ROUTE] and not first_condition_through_education(p) for p in qualifying + ], + "20 weeks": [ + p[EXTENSION] and not p[ROUTE] and not first_condition_through_education(p) + for p in qualifying + ], + } + assert all(any(rows) for rows in routes.values()) + for year in INCOME_SUPPORT_IN_PAYMENT: + units = [ + [person(45, is_claimant_or_partner=True, **{ROUTE: False}), p] + for p in reporting + ] + status = simulate(units, year).calculate(QYP, year)[1::2] + np.testing.assert_array_equal( + status, + [qualifying_young_person_given_income_support(p, year) for p in reporting], + ) + if INCOME_SUPPORT_IN_PAYMENT[year]: + assert not status.any(), year + else: + assert status.all(), year + + +def test_income_support_does_not_end_the_status_once_neutralised(): + # Invariant 5: a reform that removes Income Support leaves no award. + rows = [ + person(16, income_support_reported=3_000, **{ROUTE: True}), + person( + 17, + current_education="UPPER_SECONDARY", + income_support_reported=3_000, + **{ROUTE: False}, + ), + person(17, income_support_reported=3_000, **{ROUTE: False, EXTENSION: True}), + ] + units = [ + [person(45, is_claimant_or_partner=True, **{ROUTE: False}), p] for p in rows + ] + assert not simulate(units, 2018).calculate(QYP, 2018)[1::2].any() + removed = simulate(units, 2018) + removed.tax_benefit_system.neutralize_variable("income_support") + removed.delete_arrays("income_support") + assert removed.calculate(QYP, 2018)[1::2].all() + + +def receiving_education_or_training(p): + # Education status stands in for full-time non-advanced education that is + # not by virtue of employment (reg. 5(3)(a)), or approved training ((ab)). + return ( + p["current_education"] not in {"NOT_IN_EDUCATION", "TERTIARY"} + and not p["is_apprentice"] + ) or p["is_in_approved_training"] + + +def has_ceased_education_or_training(p): + # Reg. 5(4)(a), (5): full-time education at any level, an apprentice's + # course included, or approved training, has not ceased. + in_full_time_education = p["current_education"] != "NOT_IN_EDUCATION" + return not (in_full_time_education or p["is_in_approved_training"]) + + +def first_condition_through_education(p): + # Reg. 5(3A): begun before 19. + begun_before_19 = ( + p["age"] < 19 or p["age_started_or_accepted_current_education_or_training"] < 19 + ) + return receiving_education_or_training(p) and begun_before_19 + + +def qualifying_young_person(p): + # https://www.legislation.gov.uk/uksi/2002/2007/regulation/5 + if not 16 <= p["age"] < 20 or p["receives_benefits_in_own_right"]: + return False + if first_condition_through_education(p): + return True + ceased = has_ceased_education_or_training(p) + # Reg. 2(1): remunerative work is 24 hours a week or more. + working = p["weekly_hours"] >= 24 + # Reg. 4(2): the period needs only the second condition. + if p[ROUTE] and not (ceased and working): + return True + # Reg. 5(3)(b): under 18, having ceased, and reg. 5(4)(a). + return p["age"] < 18 and p[EXTENSION] and ceased and not working + + +GRID = [ + person( + age, + current_education=education, + is_in_approved_training=training, + is_apprentice=apprentice, + receives_benefits_in_own_right=own_right, + weekly_hours=hours, + age_started_or_accepted_current_education_or_training=entry_age, + **{ROUTE: route, EXTENSION: extension}, + ) + for age, education, training, apprentice, own_right, hours, entry_age in product( + range(14, 22), + EDUCATIONS, + (False, True), + (False, True), + (False, True), + (0, 23, 24), + # A course begun at 19 fails reg. 5(3A). Such a student has not left + # education, so neither leaver fact reaches them. + (17, 19), + ) + for route, extension in product((False, True), repeat=2) +] + + +def test_status_is_the_first_condition_or_a_leaver_fact_under_the_second(): + units = [ + [person(45, is_claimant_or_partner=True, **{ROUTE: False}), p] for p in GRID + ] + sim = simulate(units) + status = sim.calculate(QYP, YEAR)[1::2] + # Invariant 2. + np.testing.assert_array_equal(status, [qualifying_young_person(p) for p in GRID]) + age = np.array([p["age"] for p in GRID]) + hours = np.array([p["weekly_hours"] for p in GRID]) + student = np.array([receiving_education_or_training(p) for p in GRID]) + ceased = np.array([has_ceased_education_or_training(p) for p in GRID]) + route = np.array([p[ROUTE] for p in GRID]) + own_right = np.array([p["receives_benefits_in_own_right"] for p in GRID]) + assert not status[(age < 16) | (age >= 20)].any() + assert not status[own_right].any() + # A student whose course began at 19 does not qualify through the 20 + # weeks, which need them to have ceased. (The grid sets the reg. 4 fact + # directly; from a date of birth it is never true at 19.) + late_student = np.array( + [ + receiving_education_or_training(p) + and p["age"] >= 19 + and p["age_started_or_accepted_current_education_or_training"] >= 19 + and not p[ROUTE] + for p in GRID + ] + ) + assert late_student.any() and not status[late_student].any() + # Remunerative work ends the status of a person who has ceased education + # or training, and not a student's. + assert not status[ceased & (hours >= 24)].any() + assert status[student & (hours >= 24)].any() + # An advanced student in the reg. 4 period qualifies whatever their hours. + advanced = np.array([p["current_education"] == "TERTIARY" for p in GRID]) + in_age = (age >= 16) & (age < 20) & ~own_right + advanced_in_route = advanced & ~ceased & route & in_age + assert advanced_in_route.any() and status[advanced_in_route].all() + assert (advanced_in_route & (hours >= 24)).any() + # The 20 weeks alone do not reach an 18-year-old. + alone = np.array([p[EXTENSION] and not p[ROUTE] for p in GRID]) + assert not status[alone & ~student & (age >= 18)].any() + assert status[alone & ~student & (age == 17)].any() + # With the leaver facts off, the education condition alone, as before. + off = np.array([not (p[ROUTE] or p[EXTENSION]) for p in GRID]) + np.testing.assert_array_equal( + status[off], + [ + 16 <= p["age"] < 20 + and not p["receives_benefits_in_own_right"] + and first_condition_through_education(p) + for p, o in zip(GRID, off) + if o + ], + ) + # Invariant 3: rows are ordered (route, extension) within blocks of 4. + blocks = status.reshape(-1, 4) + for less, more in ((0, 1), (0, 2), (1, 3), (2, 3)): + assert (blocks[:, more] >= blocks[:, less]).all(), (less, more) + # Invariant 4, for members who are not the claimant. + family = sim.calculate( + "is_child_or_qualifying_young_person_for_child_tax_credit", YEAR + ) + np.testing.assert_array_equal(family[1::2], (age < 16) | status) + assert not family[0::2].any() + + +member = st.builds( + person, + st.integers(0, 21), + 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(), + weekly_hours=st.sampled_from([0, 10, 23, 24, 40]), + income_support_reported=st.sampled_from([0, 0, 3_000]), + **{ROUTE: st.booleans(), EXTENSION: st.booleans()}, +) + + +@st.composite +def benefit_units(draw): + claimants = draw(st.lists(member, min_size=1, max_size=2)) + others = draw(st.lists(member, max_size=4)) + # Claimants report no Income Support, so the family claims none and the + # model calculates no award; another member's report is their own claim. + return [ + dict(p, is_claimant_or_partner=True, income_support_reported=0) + for p in claimants + ] + others + + +@SETTINGS +@given( + st.lists(benefit_units(), min_size=1, max_size=6), + st.sampled_from(sorted(INCOME_SUPPORT_IN_PAYMENT)), +) +def test_family_members_in_any_benefit_unit(units, year): + # Invariants 2, 4 and 5 on benefit units of any shape, where a claimant or + # partner can be a qualifying young person by status. + sim = simulate(units, year) + people = [p for unit in units for p in unit] + status = sim.calculate(QYP, year) + expected_status = [ + qualifying_young_person_given_income_support(p, year) for p in people + ] + np.testing.assert_array_equal(status, expected_status) + family = sim.calculate( + "is_child_or_qualifying_young_person_for_child_tax_credit", year + ) + expected = [ + (p["age"] < 16 or qualifying_young_person_given_income_support(p, year)) + and not p["is_claimant_or_partner"] + and not p["is_looked_after_by_local_authority"] + for p in people + ] + np.testing.assert_array_equal(family, expected) diff --git a/policyengine_uk/tests/test_income_support_eligibility_properties.py b/policyengine_uk/tests/test_income_support_eligibility_properties.py index cb3319414a..4044de320b 100644 --- a/policyengine_uk/tests/test_income_support_eligibility_properties.py +++ b/policyengine_uk/tests/test_income_support_eligibility_properties.py @@ -211,8 +211,10 @@ def families(draw): def excluded_members(draw): """An adult who is neither claimant, partner nor in the family. - They are not in education, so a 16 to 19 year old is not a qualifying - young person. Most are primed with what barred or opened the claim when + They are not in education and past the 31 August following their 16th + birthday (Child Benefit (General) Regulations 2006 reg. 4), so a 16 to 19 + year old is not a qualifying young person. Most are primed with what + barred or opened the claim when every member counted: over state pension age, income-related ESA, an Income Support report, or caring. Some are primed with what the s.124(1)(g) proxy reads for a couple: over the qualifying age, with @@ -221,6 +223,7 @@ def excluded_members(draw): adult = { **draw(adult_inputs(min_age=16)), "current_education": "NOT_IN_EDUCATION", + "is_before_first_september_following_16th_birthday": False, } primed = draw( st.sampled_from(["random", "elderly", "esa", "award", "carer", "pension"]) diff --git a/policyengine_uk/tests/test_legacy_award_readers_properties.py b/policyengine_uk/tests/test_legacy_award_readers_properties.py index 602a71ac26..d6696f346c 100644 --- a/policyengine_uk/tests/test_legacy_award_readers_properties.py +++ b/policyengine_uk/tests/test_legacy_award_readers_properties.py @@ -63,8 +63,9 @@ (esa_income_eligible and jsa_income_eligible, read independently from the law in legacy_award_work_reference). The screens also end the award once the family's legacy benefits have closed (legacy_benefits_closed, #2119), - for their own award only; no family here reaches that in 2025, when - Income Support and income-based JSA are open and nobody reports tax credits; + and the model then ends every member's status too; no family here reaches + that in 2025, when Income Support and income-based JSA are open and + nobody reports tax credits; - a person is on an award exactly when they are the payee of their couple's positive award (the claimant or partner who reports it, or the claimant) or, for anyone else, when their own claim passes: they are not engaged in diff --git a/policyengine_uk/utils/benefit_unit.py b/policyengine_uk/utils/benefit_unit.py index 06ed4411b8..2060265cbf 100644 --- a/policyengine_uk/utils/benefit_unit.py +++ b/policyengine_uk/utils/benefit_unit.py @@ -54,8 +54,8 @@ def has_sixteen_year_old_dependant(benunit, period): those dates, so the benefit cap rates, which turn on responsibility for a child or young person, count such a member for the year. The Housing Benefit cap exceptions read the model's Child Benefit qualifying young - person instead, which leaves out a 16-year-old in neither non-advanced - education nor approved training (#2287). + person, which counts a 16-year-old for the year too, whatever their + education (Child Benefit (General) Regulations 2006 reg. 4). """ person = benunit.members age = person("age", period) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_specified_benefit.py b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_specified_benefit.py index 4f629c776a..689b2250ab 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_specified_benefit.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_exempt_specified_benefit.py @@ -62,16 +62,13 @@ class is_housing_benefit_benefit_cap_exempt_specified_benefit(Variable): "Universal Credit before the cap (universal_credit_pre_benefit_cap), " "so a Universal Credit award the cap reduces to nil still lifts the " "Housing Benefit cap. A young person is the model's Child Benefit " - "qualifying young person, which leaves out a 16-year-old in neither " - "non-advanced education nor approved training. The Housing Benefit " - "cap rate (is_housing_benefit_benefit_cap_single_claimant_rate) " - "counts such a 16-year-old for the year, so a lone parent with one " - "gets the family rate, but the 16-year-old's own disability living " - "allowance or personal independence payment does not lift the cap. " - "In law one status decides both: a school leaver remains a " - "qualifying young person until a terminal date or the end of an " - "extension period (Child Benefit (General) Regulations 2006 regs. 5 " - "and 7), which the model does not place (#2287)." + "qualifying young person (HB Regs 2006 reg. 19), which includes a " + "16-year-old who has left education, until the 31 August after their " + "16th birthday (Child Benefit (General) Regulations 2006 reg. 4), and " + "a school leaver in an extension period or before a terminal date " + "where those inputs are set (regs. 5 and 7). Such a young person's " + "own disability living allowance, personal independence payment or " + "armed forces independence payment lifts the cap." ) definition_period = YEAR reference = ( @@ -88,10 +85,9 @@ def formula(benunit, period, parameters): # A child under 16 or a Child Benefit qualifying young person the # claimant or partner is responsible for (HB Regs 2006 regs 2(1), 19). - # A 16-year-old in neither non-advanced education nor approved - # training is not one here, although the cap rate counts them for the - # year (has_sixteen_year_old_dependant); the model's qualifying young - # person has no school-leaver terminal date (#2287). + # A 16-year-old who has left education is one for the year (Child + # Benefit (General) Regulations 2006 reg. 4), as the cap rate counts + # them (has_sixteen_year_old_dependant). child_or_young_person = person( "is_child_or_young_person_for_legacy_benefits", period ) diff --git a/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_single_claimant_rate.py b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_single_claimant_rate.py index 9d5daa2c20..562729465f 100644 --- a/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_single_claimant_rate.py +++ b/policyengine_uk/variables/gov/dwp/housing_benefit/benefit_cap/is_housing_benefit_benefit_cap_single_claimant_rate.py @@ -15,11 +15,10 @@ class is_housing_benefit_benefit_cap_single_claimant_rate(Variable): "the other rate even where the partner could not be a joint claimant " "for Universal Credit. A 16-year-old member counts as a young person " "for the year (reg. 19(1); Child Benefit (General) Regulations 2006 " - "regs. 5 and 7). The exceptions " + "regs. 4, 5 and 7). The exceptions " "(is_housing_benefit_benefit_cap_exempt_specified_benefit) read the " - "model's Child Benefit qualifying young person instead, which leaves " - "out a 16-year-old in neither non-advanced education nor approved " - "training (#2287)." + "model's Child Benefit qualifying young person, which counts a " + "16-year-old for the year too, whatever their education (reg. 4)." ) definition_period = YEAR reference = ( diff --git a/policyengine_uk/variables/gov/dwp/is_in_child_tax_credit_extension_period.py b/policyengine_uk/variables/gov/dwp/is_in_child_tax_credit_extension_period.py new file mode 100644 index 0000000000..de6be75a66 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_in_child_tax_credit_extension_period.py @@ -0,0 +1,21 @@ +from policyengine_uk.model_api import * + + +class is_in_child_tax_credit_extension_period(Variable): + value_type = bool + entity = Person + label = "In a Child Tax Credit extension period" + documentation = ( + "Whether, for some of the year, the person has ceased full-time " + "education or approved training, HMRC was notified within three " + "months that they are registered for work or training with a " + "qualifying body (the Careers Service, Connexions or the Ministry of " + "Defence), and no more than 20 weeks have passed since they left " + "(Child Tax Credit Regulations 2002 reg. 5(3)(b)(i)-(iii)). The model " + "applies the age limit (reg. 5(3)(b)) and the second condition (reg. " + "5(4)). Surveys do not record the registration, so this is false " + "unless set." + ) + definition_period = YEAR + default_value = False + reference = "https://www.legislation.gov.uk/uksi/2002/2007/regulation/5" diff --git a/policyengine_uk/variables/gov/dwp/is_in_remunerative_work_for_child_tax_credit.py b/policyengine_uk/variables/gov/dwp/is_in_remunerative_work_for_child_tax_credit.py new file mode 100644 index 0000000000..5369423124 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_in_remunerative_work_for_child_tax_credit.py @@ -0,0 +1,20 @@ +from policyengine_uk.model_api import * + + +class is_in_remunerative_work_for_child_tax_credit(Variable): + value_type = bool + entity = Person + label = "In remunerative work for Child Tax Credit" + documentation = ( + "Whether the person works at least 24 hours a week for payment or in " + "expectation of payment (Child Tax Credit Regulations 2002 reg. " + "2(1)), read from average weekly hours over the year. The Working " + "Tax Credit rules the definition refers to, for counting hours and " + "for excluded work, are not modelled." + ) + definition_period = YEAR + reference = "https://www.legislation.gov.uk/uksi/2002/2007/regulation/2" + + def formula(person, period, parameters): + p = parameters(period).gov.dwp.tax_credits.child_tax_credit.eligibility + return person("weekly_hours", period) >= p.remunerative_work_hours diff --git a/policyengine_uk/variables/gov/dwp/is_qualifying_young_person_for_child_tax_credit.py b/policyengine_uk/variables/gov/dwp/is_qualifying_young_person_for_child_tax_credit.py index 2c5e98dd46..53d39b176e 100644 --- a/policyengine_uk/variables/gov/dwp/is_qualifying_young_person_for_child_tax_credit.py +++ b/policyengine_uk/variables/gov/dwp/is_qualifying_young_person_for_child_tax_credit.py @@ -1,26 +1,99 @@ from policyengine_uk.model_api import * +from policyengine_uk.variables.household.demographic.highest_education import ( + EducationType, +) class is_qualifying_young_person_for_child_tax_credit(Variable): value_type = bool entity = Person label = "Qualifying young person for Child Tax Credit" + documentation = ( + "A person aged 16 to 19 who meets the Child Tax Credit Regulations " + "2002's conditions. The first condition (reg. 5(3)) is full-time " + "non-advanced education or approved training, begun before 19, or, " + "under 18, the 20 weeks after leaving while registered for work or " + "training. Reg. 4(1) lifts the first condition from the 16th " + "birthday until the 31 August that next follows it. That period " + "reads is_before_first_september_following_16th_birthday, so it " + "counts in any year that starts before the 31 August. For a 16th " + "birthday on 31 August, reg. 4(1A) gives no such period, where " + "Universal Credit gives that day and Child Benefit two: the year " + "counts all the same, as the one in which the person is a child " + "until 30 August, and not the year after. The second condition " + "(reg. 5(4)) always applies: a young person who has ceased full-time " + "education or approved training must not be in remunerative work, and " + "no one may be receiving a benefit in their own right. Full-time " + "education at any level counts (reg. 5(5)), so an advanced student " + "has not ceased it, nor has an apprentice still on a course; the " + "employment exclusion is part of the first condition only. Reg. " + "5(4)(c) bars every route for a person who receives Income Support, " + "and the formula reads is_on_income_support for it: the benefit " + "unit's calculated award, for whichever of the claimant and partner " + "receives it, and their own reported award for any other member " + "while Income Support is in payment and no reform neutralises it. A " + "reform that replaces Income Support's formula with zero ends a " + "claimant's or partner's receipt; for another member it must set " + "gov.dwp.income_support.active to false, neutralise income_support " + "or set their reported award to zero. The own-right benefits input " + "covers Universal Credit, Employment and Support Allowance and " + "Jobseeker's Allowance of either kind, where reg. 5(4)(c) lists only " + "Universal Credit and the income-related and income-based " + "allowances. Interruptions (reg. 5(7)) are not modelled." + ) definition_period = YEAR - reference = "https://www.legislation.gov.uk/uksi/2002/2007/regulation/5" + reference = [ + "https://www.legislation.gov.uk/uksi/2002/2007/regulation/4", + "https://www.legislation.gov.uk/uksi/2002/2007/regulation/5", + ] def formula(person, period, parameters): + p = parameters(period).gov.dwp.tax_credits.child_tax_credit.eligibility + # Reg. 5(3)(a), (ab) and (3A). + receiving_education_or_training = person( + "meets_qualifying_young_person_education_or_training_condition_for_child_tax_credit", + period, + ) + in_education_or_training = receiving_education_or_training & person( + "meets_qualifying_young_person_entry_condition_for_child_tax_credit", + period, + ) + # Reg. 4(1): from the 16th birthday to the 31 August that next + # follows. + before_31_august_following_16th_birthday = person( + "is_before_first_september_following_16th_birthday", period + ) + # Reg. 5(3)(b): 20 weeks after leaving, under 18. + in_extension_period = ( + person("age", period) < p.extension_period_age_limit + ) & person("is_in_child_tax_credit_extension_period", period) + # Reg. 5(4)(a) bars remunerative work only for a person "having + # ceased to receive full-time education or approved training". + # Full-time education at a school or college counts at any level + # (reg. 5(5)), so failing the non-advanced test of reg. 5(3)(a) does + # not mean having ceased it. + # Education received by virtue of employment is left out of the first + # condition (reg. 5(3)(a)), which reg. 4(2) lifts, and not out of reg. + # 5(5): an apprentice still on a course has not ceased it. + in_full_time_education = ( + person("current_education", period) != EducationType.NOT_IN_EDUCATION + ) + ceased = ~in_full_time_education & ~person("is_in_approved_training", period) + working = person("is_in_remunerative_work_for_child_tax_credit", period) + # Reg. 4(2): the period needs no first condition, only the second. + in_regulation_4_period = before_31_august_following_16th_birthday & ~( + ceased & working + ) + # Reg. 5(3)(b) is for a person who has ceased. + in_20_weeks = in_extension_period & ceased & ~working return ( person( "meets_qualifying_young_person_age_condition_for_child_tax_credit", period, ) - & person( - "meets_qualifying_young_person_education_or_training_condition_for_child_tax_credit", - period, - ) - & person( - "meets_qualifying_young_person_entry_condition_for_child_tax_credit", - period, - ) + & (in_education_or_training | in_regulation_4_period | in_20_weeks) + # Reg. 5(4)(c): Universal Credit, ESA and JSA in their own right, + # and Income Support, which reg. 4(2) keeps for the reg. 4 period. & ~person("receives_benefits_in_own_right", period) + & ~person("is_on_income_support", period) ) diff --git a/policyengine_uk/variables/gov/hmrc/child_benefit_child_index.py b/policyengine_uk/variables/gov/hmrc/child_benefit_child_index.py index fbf470039c..785e042a10 100644 --- a/policyengine_uk/variables/gov/hmrc/child_benefit_child_index.py +++ b/policyengine_uk/variables/gov/hmrc/child_benefit_child_index.py @@ -8,7 +8,7 @@ class child_benefit_child_index(Variable): definition_period = YEAR def formula(person, period, parameters): - child = person("is_child_or_qualifying_young_person_for_child_benefit", period) + child = person("is_child_benefit_payable_for_person", period) rank = ( person.get_rank( person.benunit, diff --git a/policyengine_uk/variables/gov/hmrc/child_benefit_respective_amount.py b/policyengine_uk/variables/gov/hmrc/child_benefit_respective_amount.py index e8e66493c9..3f5b702adc 100644 --- a/policyengine_uk/variables/gov/hmrc/child_benefit_respective_amount.py +++ b/policyengine_uk/variables/gov/hmrc/child_benefit_respective_amount.py @@ -12,7 +12,7 @@ class child_benefit_respective_amount(Variable): "https://www.legislation.gov.uk/ukpga/1992/4/part/IX", "https://www.legislation.gov.uk/uksi/2006/965/regulation/2", ) - defined_for = "is_child_or_qualifying_young_person_for_child_benefit" + defined_for = "is_child_benefit_payable_for_person" def formula(person, period, parameters): eligible = True diff --git a/policyengine_uk/variables/gov/hmrc/is_before_child_benefit_terminal_date.py b/policyengine_uk/variables/gov/hmrc/is_before_child_benefit_terminal_date.py new file mode 100644 index 0000000000..64f48ee6e7 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/is_before_child_benefit_terminal_date.py @@ -0,0 +1,20 @@ +from policyengine_uk.model_api import * + + +class is_before_child_benefit_terminal_date(Variable): + value_type = bool + entity = Person + label = "Before the Child Benefit terminal date after leaving education" + documentation = ( + "Whether, for some of the year, the person has left relevant " + "education or approved training and the week that includes their " + "terminal date has not ended. The terminal date is the last day of " + "February, May, August or November, whichever comes first after they " + "left, or after their last external examination if they were entered " + "for one (Child Benefit (General) Regulations 2006 reg. 7(2), Cases 1 " + "and 2). The model holds one education status for the year and no " + "leaving date, so this is false unless set." + ) + definition_period = YEAR + default_value = False + reference = "https://www.legislation.gov.uk/uksi/2006/223/regulation/7" diff --git a/policyengine_uk/variables/gov/hmrc/is_child_benefit_payable_for_person.py b/policyengine_uk/variables/gov/hmrc/is_child_benefit_payable_for_person.py new file mode 100644 index 0000000000..8114bf40e2 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/is_child_benefit_payable_for_person.py @@ -0,0 +1,55 @@ +from policyengine_uk.model_api import * + + +class is_child_benefit_payable_for_person(Variable): + value_type = bool + entity = Person + label = "Child Benefit is payable for this person" + documentation = ( + "Whether the benefit unit's Child Benefit includes an amount for this " + "person: they are its child or qualifying young person, and Child " + "Benefit is not barred by remunerative work. Child Benefit is not " + "payable for a qualifying young person 'by virtue of' the terminal " + "date rule for any week they are in remunerative work (Child Benefit " + "(General) Regulations 2006 reg. 7(3)). So a young person who " + "qualifies only through the terminal date, and works 24 hours or " + "more a week, stays a qualifying young person (and a Housing Benefit " + "young person) but brings no Child Benefit. One who also meets " + "another condition is paid through that one (reg. 2(2))." + ) + definition_period = YEAR + reference = ( + "https://www.legislation.gov.uk/uksi/2006/223/regulation/2", + "https://www.legislation.gov.uk/uksi/2006/223/regulation/7", + ) + + def formula(person, period, parameters): + another_condition = ( + ( + person( + "meets_qualifying_young_person_education_or_training_condition_for_child_benefit", + period, + ) + & person( + "meets_qualifying_young_person_entry_condition_for_child_benefit", + period, + ) + ) + | person("is_before_first_september_following_16th_birthday", period) + | person( + "meets_qualifying_young_person_extension_period_condition_for_child_benefit", + period, + ) + ) + only_through_terminal_date = ( + person("is_qualifying_young_person_for_child_benefit", period) + & ~person("is_child_for_child_benefit", period) + & ~another_condition + ) + barred = only_through_terminal_date & person( + "is_in_remunerative_work_for_child_benefit", period + ) + return ( + person("is_child_or_qualifying_young_person_for_child_benefit", period) + & ~barred + ) diff --git a/policyengine_uk/variables/gov/hmrc/is_in_child_benefit_extension_period.py b/policyengine_uk/variables/gov/hmrc/is_in_child_benefit_extension_period.py new file mode 100644 index 0000000000..beb5ed3938 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/is_in_child_benefit_extension_period.py @@ -0,0 +1,23 @@ +from policyengine_uk.model_api import * + + +class is_in_child_benefit_extension_period(Variable): + value_type = bool + entity = Person + label = "In a Child Benefit extension period" + documentation = ( + "Whether the person is in a Child Benefit extension period in the " + "year: they have ceased education or training, are registered for " + "work, education or training with a qualifying body (the Careers " + "Service, Connexions or the Ministry of Defence), the person " + "responsible for them was entitled to Child Benefit for them " + "immediately before and asked for the extension within three months, " + "and the 20 weeks from the week after they left have not run out " + "(Child Benefit (General) Regulations 2006 reg. 5(2)(a), (b), (d), " + "(e) and (f), and (3)). The model applies the age limit (reg. 5(1)) " + "and the bar on remunerative work (reg. 5(2)(c)). Surveys do not " + "record registration or the request, so this is false unless set." + ) + definition_period = YEAR + default_value = False + reference = "https://www.legislation.gov.uk/uksi/2006/223/regulation/5" diff --git a/policyengine_uk/variables/gov/hmrc/is_in_remunerative_work_for_child_benefit.py b/policyengine_uk/variables/gov/hmrc/is_in_remunerative_work_for_child_benefit.py new file mode 100644 index 0000000000..10a05ee191 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/is_in_remunerative_work_for_child_benefit.py @@ -0,0 +1,18 @@ +from policyengine_uk.model_api import * + + +class is_in_remunerative_work_for_child_benefit(Variable): + value_type = bool + entity = Person + label = "In remunerative work for Child Benefit" + documentation = ( + "Whether the person works at least 24 hours a week for payment or in " + "expectation of payment (Child Benefit (General) Regulations 2006 " + "reg. 1(3)), read from average weekly hours over the year." + ) + definition_period = YEAR + reference = "https://www.legislation.gov.uk/uksi/2006/223/regulation/1" + + def formula(person, period, parameters): + p = parameters(period).gov.hmrc.child_benefit.eligibility + return person("weekly_hours", period) >= p.remunerative_work_hours diff --git a/policyengine_uk/variables/gov/hmrc/is_qualifying_young_person_for_child_benefit.py b/policyengine_uk/variables/gov/hmrc/is_qualifying_young_person_for_child_benefit.py index 898a91b7cd..efc736e199 100644 --- a/policyengine_uk/variables/gov/hmrc/is_qualifying_young_person_for_child_benefit.py +++ b/policyengine_uk/variables/gov/hmrc/is_qualifying_young_person_for_child_benefit.py @@ -5,25 +5,85 @@ class is_qualifying_young_person_for_child_benefit(Variable): value_type = bool entity = Person label = "Qualifying young person for Child Benefit" + documentation = ( + "A person aged 16 to 19 who meets any of the conditions in Child " + "Benefit (General) Regulations 2006 regs 3 to 7 (reg. 2(2): a " + "qualifying young person until the last of them ceases to be met) " + "and is not receiving a benefit in their own right (reg. 8). The " + "conditions modelled are: non-advanced education or approved " + "training, begun before 19 (reg. 3); the period to the 31 August " + "after the 16th birthday, whatever their education (reg. 4); an " + "extension period for a 16- or 17-year-old (reg. 5); and the period " + "to the terminal date after leaving education (reg. 7). Reg. 4 " + "reads is_before_first_september_following_16th_birthday: its 31 " + "August (1 September for a 16th birthday on 31 August) falls in the " + "same fiscal year as Universal Credit's 1 September, so a year that " + "starts before one starts before the other. Interruptions (reg. 6) " + "are not modelled. Reg. 8 reads the own-right benefits input, which " + "covers Universal Credit, Employment and Support Allowance and " + "Jobseeker's Allowance, the person's own reported Universal " + "Credit, Employment and Support Allowance of either kind and " + "income-based Jobseeker's Allowance, the awards reg. 8(2)(b), (e) " + "and (f) list, and receives_income_support_in_own_right (reg. " + "8(2)(a)). It does not read the incapacity benefit and tax credits " + "that reg. 8 also lists. A positive report for the year ends the " + "status for the whole year." + ) definition_period = YEAR reference = [ "https://www.legislation.gov.uk/ukpga/1992/4/section/142", - "https://www.legislation.gov.uk/uksi/2006/223", + "https://www.legislation.gov.uk/uksi/2006/223/regulation/2", + "https://www.legislation.gov.uk/uksi/2006/223/regulation/3", + "https://www.legislation.gov.uk/uksi/2006/223/regulation/4", + "https://www.legislation.gov.uk/uksi/2006/223/regulation/5", + "https://www.legislation.gov.uk/uksi/2006/223/regulation/7", + "https://www.legislation.gov.uk/uksi/2006/223/regulation/8", ] def formula(person, period, parameters): + # Reg. 3: education or training, begun before 19. + in_education_or_training = person( + "meets_qualifying_young_person_education_or_training_condition_for_child_benefit", + period, + ) & person( + "meets_qualifying_young_person_entry_condition_for_child_benefit", + period, + ) + # Reg. 4: until the 31 August after the 16th birthday. A person in + # that period is under 17, as reg. 4(1) requires. + before_31_august_following_16th_birthday = person( + "is_before_first_september_following_16th_birthday", period + ) + # Reg. 5: the extension period. + in_extension_period = person( + "meets_qualifying_young_person_extension_period_condition_for_child_benefit", + period, + ) + # Reg. 7: until the terminal date after leaving. + before_terminal_date = person("is_before_child_benefit_terminal_date", period) + # Reg. 8(2)(b), (e) and (f): income-based JSA, ESA and Universal + # Credit. Reports are inputs, so reading them makes no cycle. + receives_own_benefits = ( + person("receives_benefits_in_own_right", period) + | (person("universal_credit_reported", period) > 0) + | (person("esa_income_reported", period) > 0) + | (person("esa_contrib_reported", period) > 0) + | (person("jsa_income_reported", period) > 0) + ) return ( person( "meets_qualifying_young_person_age_condition_for_child_benefit", period, ) - & person( - "meets_qualifying_young_person_education_or_training_condition_for_child_benefit", - period, - ) - & person( - "meets_qualifying_young_person_entry_condition_for_child_benefit", - period, + & ( + in_education_or_training + | before_31_august_following_16th_birthday + | in_extension_period + | before_terminal_date ) - & ~person("receives_benefits_in_own_right", period) + & ~receives_own_benefits + # Reg. 8(2)(a). The reported award, not is_on_income_support: + # the family's Income Support reads this test, so the calculated + # award would make a cycle. + & ~person("receives_income_support_in_own_right", period) ) diff --git a/policyengine_uk/variables/gov/hmrc/meets_qualifying_young_person_extension_period_condition_for_child_benefit.py b/policyengine_uk/variables/gov/hmrc/meets_qualifying_young_person_extension_period_condition_for_child_benefit.py new file mode 100644 index 0000000000..592e0189f7 --- /dev/null +++ b/policyengine_uk/variables/gov/hmrc/meets_qualifying_young_person_extension_period_condition_for_child_benefit.py @@ -0,0 +1,24 @@ +from policyengine_uk.model_api import * + + +class meets_qualifying_young_person_extension_period_condition_for_child_benefit( + Variable +): + value_type = bool + entity = Person + label = "Meets qualifying young person extension period condition for Child Benefit" + documentation = ( + "A person under 18 who has left education or training, is registered " + "with a qualifying body and is not in remunerative work, for the 20 " + "weeks of the extension period their parent or carer asked for." + ) + definition_period = YEAR + reference = "https://www.legislation.gov.uk/uksi/2006/223/regulation/5" + + def formula(person, period, parameters): + p = parameters(period).gov.hmrc.child_benefit.eligibility + return ( + (person("age", period) < p.extension_period_age_limit) + & person("is_in_child_benefit_extension_period", period) + & ~person("is_in_remunerative_work_for_child_benefit", period) + ) diff --git a/policyengine_uk/variables/household/income/receives_income_support_in_own_right.py b/policyengine_uk/variables/household/income/receives_income_support_in_own_right.py new file mode 100644 index 0000000000..98b89e96a2 --- /dev/null +++ b/policyengine_uk/variables/household/income/receives_income_support_in_own_right.py @@ -0,0 +1,40 @@ +from policyengine_uk.model_api import * + + +class receives_income_support_in_own_right(Variable): + value_type = bool + entity = Person + label = "Receives Income Support in own right" + documentation = ( + "Whether this person receives Income Support on a claim of their own, " + "as the Child Benefit qualifying young person test reads it (Child " + "Benefit (General) Regulations 2006 reg. 8(2)(a)); through that test " + "it also ends the Housing Benefit and legacy young person. It is the " + "person's own reported Income Support (income_support_reported) " + "while Income Support is in payment (gov.dwp.income_support.active) " + "and no reform neutralises income_support; set it directly otherwise. " + "For a member who is not the claimant or partner, " + "is_on_income_support reads the same report. For a claimant or " + "partner, is_on_income_support reads the benefit unit's calculated " + "award, for whichever of the claimant and partner receives it, but " + "that award depends on who the family's Child Benefit young persons " + "are, so this variable cannot read it without a cycle. A reform that " + "removes Income Support must set gov.dwp.income_support.active to " + "false, neutralise income_support, or set reported awards to zero, " + "for the Child Benefit status to follow; replacing its formula with " + "zero leaves this variable true. Pension Credit (SPC Regs 2002 reg. " + "4A(5)) and Child Tax Credit (CTC Regs 2002 reg. 5(4)(c)) read " + "is_on_income_support instead, so they follow such a reform for a " + "claimant or partner. Universal Credit's list (UC Regs 2013 reg. " + "5(5)) does not include Income Support." + ) + definition_period = YEAR + reference = "https://www.legislation.gov.uk/uksi/2006/223/regulation/8" + + def formula(person, period, parameters): + active = parameters(period).gov.dwp.income_support.active + # A reform that removes Income Support leaves no award to receive. + removed = person.simulation.tax_benefit_system.get_variable( + "income_support" + ).is_neutralized + return active & (not removed) & (person("income_support_reported", period) > 0)