diff --git a/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/childcare_entitlement_hours.yaml b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/childcare_entitlement_hours.yaml new file mode 100644 index 000000000..2e4c7abfd --- /dev/null +++ b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/childcare_entitlement_hours.yaml @@ -0,0 +1,40 @@ +description: The Department for Education provides this number of free childcare hours per week through the extended childcare entitlement scheme, depending on the child's age. +metadata: + type: single_amount + threshold_unit: year + amount_unit: hour + label: Extended childcare entitlement hours by age + reference: + - title: Childcare Act 2016 - chapter 5.1 + href: https://www.legislation.gov.uk/ukpga/2016/5 and https://www.gov.uk/check-eligible-free-childcare-if-youre-working + +brackets: + - threshold: + 2015-01-01: 0 + amount: + 2015-01-01: 0 + - threshold: + 2015-01-01: 1 + 2025-01-01: 0.75 + amount: + 2015-01-01: 0 + 2025-01-01: 15 + 2026-01-01: 30 + - threshold: + 2015-01-01: 2 + amount: + 2015-01-01: 0 + 2024-01-01: 15 + 2026-01-01: 30 + - threshold: + 2015-01-01: 3 + amount: + 2015-01-01: 30 + - threshold: + 2015-01-01: 4 + amount: + 2015-01-01: 30 + - threshold: + 2015-01-01: 5 + amount: + 2015-01-01: 0 \ No newline at end of file diff --git a/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/expense_rate.yaml b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/expense_rate.yaml new file mode 100644 index 000000000..bba0e3c43 --- /dev/null +++ b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/expense_rate.yaml @@ -0,0 +1,28 @@ +description: The Department for Education sets hourly rates to convert childcare hours into expenses for extended childcare entitlement. These rates vary by child's age and are used to calculate the funding provided to childcare providers. + +metadata: + type: single_amount + threshold_unit: year + amount_unit: currency-GBP + label: Extended childcare entitlement hourly funding rates by age + reference: + - title: Different Rates for Extended Childcare Entitlement + href: + - https://www.nurseryworld.co.uk/content/news/dfe-confirms-extended-30-hours-funding-rates-but-sector-says-they-dont-reflect-the-real-delivery-cost/ + - https://www.gov.uk/government/news/childcare-settings-receive-cash-boost-as-funding-rates-increase + - title: Commons Library + href: https://commonslibrary.parliament.uk/research-briefings/cbp-8052/ + +brackets: + - threshold: + 2015-01-01: 0 + amount: + 2015-01-01: 11.22 + - threshold: + 2015-01-01: 2 + amount: + 2015-01-01: 8.28 + - threshold: + 2015-01-01: 3 + amount: + 2015-01-01: 5.88 \ No newline at end of file diff --git a/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/income/countable_sources.yaml b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/income/countable_sources.yaml new file mode 100644 index 000000000..153e70b4b --- /dev/null +++ b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/income/countable_sources.yaml @@ -0,0 +1,12 @@ +description: The Department for Education includes these income sources when determining eligibility for extended childcare entitlement. +metadata: + reference: + - title: The Childcare Payments (Eligibility) Regulations 2015 - Part 9 and 10 + href: https://www.legislation.gov.uk/ukdsi/2015/9780111127063 + period: year + unit: program + label: Extended childcare entitlement countable income sources +values: + 2015-01-01: + - employment_income + - self_employment_income \ No newline at end of file diff --git a/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/income/maximum_adjusted_income.yaml b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/income/maximum_adjusted_income.yaml new file mode 100644 index 000000000..cf25f6498 --- /dev/null +++ b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/income/maximum_adjusted_income.yaml @@ -0,0 +1,10 @@ +description: The Department for Education limits extended childcare entitlement eligibility to households where individual adjusted income does not exceed this yearly threshold. +metadata: + unit: currency-GBP + period: year + label: Extended childcare entitlement maximum adjusted income threshold + reference: + - title: The Childcare Payments (Eligibility) Regulations 2015 - Regulation 15 + href: https://www.legislation.gov.uk/uksi/2015/448/regulation/15#commentary-key-e27c923eee152accd495af8425536e29 +values: + 2015-01-01: 100_000 \ No newline at end of file diff --git a/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/minimum_weekly_hours.yaml b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/minimum_weekly_hours.yaml new file mode 100644 index 000000000..a76e767f2 --- /dev/null +++ b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/minimum_weekly_hours.yaml @@ -0,0 +1,10 @@ +description: The Department for Education limits extended childcare entitlement to benefit units in which each spouse earns at least the product of their minimum wage and this number of hours per week. +metadata: + period: year + unit: hour + label: Extended childcare entitlement weekly work hours minimum + reference: + - title: The Childcare Payments (Eligibility) (Amendment) Regulations 2016 - regulation 3 + href: https://www.legislation.gov.uk/uksi/2016/793/regulation/3/made +values: + 2016-01-01: 16 \ No newline at end of file diff --git a/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/weeks_per_year.yaml b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/weeks_per_year.yaml new file mode 100644 index 000000000..5a9e511b0 --- /dev/null +++ b/policyengine_uk/parameters/gov/dfe/extended_childcare_entitlement/weeks_per_year.yaml @@ -0,0 +1,10 @@ +description: The Department for Education provides extended childcare entitlement for specified weeks per year. +metadata: + period: year + unit: week + label: Extended childcare entitlement weeks per year + reference: + - title: Childcare Choices document - 15 and 30 hours childcare support + href: https://www.childcarechoices.gov.uk/15-and-30-hours-childcare-support/working-families/eligibility +values: + 2016-01-01: 38 \ No newline at end of file diff --git a/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_expenses.yaml b/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_expenses.yaml new file mode 100644 index 000000000..068afdf5f --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_expenses.yaml @@ -0,0 +1,20 @@ +- name: Annual expenses for 30 hours entitlement + period: 2025 + input: + extended_childcare_entitlement_hours: 30 + output: + extended_childcare_entitlement: 6_703.2 + +- name: Annual expenses for 15 hours entitlement + period: 2025 + input: + extended_childcare_entitlement_hours: 15 + output: + extended_childcare_entitlement: 3_351.6 + +- name: No expenses for zero hours + period: 2025 + input: + extended_childcare_entitlement_hours: 0 + output: + extended_childcare_entitlement: 0 # 0 hours diff --git a/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_hours.yaml b/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_hours.yaml new file mode 100644 index 000000000..714d506ba --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_hours.yaml @@ -0,0 +1,29 @@ +- name: Eligible for 30 hours - All conditions met + period: 2025 + input: + age: 3 # Age 3-4 gets 30 hours in 2025 + is_child: false + extended_childcare_entitlement_meets_income_requirements: true + extended_childcare_entitlement_work_condition: true + output: + extended_childcare_entitlement_hours: 30 + +- name: Eligible for 15 hours - All first conditions met + period: 2025 + input: + age: 2 # Age 2-3 gets 15 hours in 2025 + is_child: false + extended_childcare_entitlement_meets_income_requirements: true + extended_childcare_entitlement_work_condition: true + output: + extended_childcare_entitlement_hours: 15 + +- name: Eligible for 15 hours - One condition did not meet + period: 2025 + input: + age: 2 + is_child: false + extended_childcare_entitlement_meets_income_requirements: false + extended_childcare_entitlement_work_condition: true + output: + extended_childcare_entitlement_hours: 0 \ No newline at end of file diff --git a/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_hours_multiple_children.yaml b/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_hours_multiple_children.yaml new file mode 100644 index 000000000..7db61c566 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_hours_multiple_children.yaml @@ -0,0 +1,59 @@ +- name: Eligible for mixed hours - Family with multiple children + period: 2025 + input: + people: + parent1: + age: 35 + is_child: false + extended_childcare_entitlement_meets_income_requirements: true + extended_childcare_entitlement_work_condition: 1 + parent2: + age: 33 + is_child: false + extended_childcare_entitlement_meets_income_requirements: true + extended_childcare_entitlement_work_condition: 1 + child1: + age: 2 + is_child: true + extended_childcare_entitlement_meets_income_requirements: true + extended_childcare_entitlement_work_condition: 0 + child2: + age: 3 + is_child: true + extended_childcare_entitlement_meets_income_requirements: true + extended_childcare_entitlement_work_condition: 0 + benunits: + benunit1: + members: [parent1, parent2, child1, child2] + output: + extended_childcare_entitlement_hours: [0, 0, 15, 30] + +- name: Not eligible - Family with multiple children but conditions not met + period: 2025 + input: + people: + parent1: + age: 35 + is_child: false + extended_childcare_entitlement_meets_income_requirements: false + extended_childcare_entitlement_work_condition: 1 + parent2: + age: 33 + is_child: false + extended_childcare_entitlement_meets_income_requirements: false + extended_childcare_entitlement_work_condition: 1 + child1: + age: 2 + is_child: true + extended_childcare_entitlement_meets_income_requirements: false + extended_childcare_entitlement_work_condition: 0 + child2: + age: 3 + is_child: true + extended_childcare_entitlement_meets_income_requirements: false + extended_childcare_entitlement_work_condition: 0 + benunits: + benunit1: + members: [parent1, parent2, child1, child2] + output: + extended_childcare_entitlement_hours: [0, 0, 0, 0] \ No newline at end of file diff --git a/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_income_condition.yaml b/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_income_condition.yaml new file mode 100644 index 000000000..5ffbf3590 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_income_condition.yaml @@ -0,0 +1,48 @@ +- name: Under 18 meeting yearly threshold + period: 2025 + input: + age: 17 + employment_income: 10_400 + output: + extended_childcare_entitlement_meets_income_requirements: true + +- name: Age 20 meeting 18-20 yearly threshold + period: 2025 + input: + age: 20 + employment_income: 22_000 + output: + extended_childcare_entitlement_meets_income_requirements: true + +- name: Age 21+ meeting higher yearly threshold + period: 2025 + input: + age: 25 + employment_income: 29_600 # Above £2,379 * 4 = £9,516 yearly threshold + output: + extended_childcare_entitlement_meets_income_requirements: true + +- name: Under 18 below yearly threshold + period: 2025 + input: + age: 16 + employment_income: 5_000 # Below £5,324 yearly threshold + output: + extended_childcare_entitlement_meets_income_requirements: false + +- name: Age 18-20 - eligible + period: 2025 + input: + age: 19 + employment_income: 8_000 + output: + extended_childcare_entitlement_meets_income_requirements: false + +- name: At maximum threshold + period: 2025 + input: + age: 35 + employment_income: 100_0000 # At assumed maximum threshold + adjusted_net_income: 100_0000 + output: + extended_childcare_entitlement_meets_income_requirements: false \ No newline at end of file diff --git a/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_work_condition.yaml b/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_work_condition.yaml new file mode 100644 index 000000000..f9dc9180b --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_work_condition.yaml @@ -0,0 +1,120 @@ +- name: Lone parent working - eldest - eligible + period: 2025 + input: + people: + parent: + age: 35 + is_adult: true + in_work: true + incapacity_benefit: 0 + child: + age: 4 + is_child: true + benunits: + benunit: + members: [parent, child] + output: + extended_childcare_entitlement_work_condition: [1, 0] + +- name: Lone parent working - younger adult - ineligible + period: 2025 + input: + people: + parent1: + age: 40 + is_adult: true + in_work: false + parent2: + age: 35 + is_adult: true + in_work: true + child: + age: 4 + is_child: true + benunits: + benunit: + members: [parent1, parent2, child] + output: + extended_childcare_entitlement_work_condition: [0, 0, 0] + +- name: Couple with children both working - eligible + period: 2025 + input: + people: + parent1: + age: 40 + is_adult: true + in_work: true + parent2: + age: 38 + is_adult: true + in_work: true + child: + age: 4 + is_child: true + benunits: + benunit: + members: [parent1, parent2, child] + output: + extended_childcare_entitlement_work_condition: [1, 1, 0] + +- name: Couple with children one working (eldest) one not - ineligible + period: 2025 + input: + people: + parent1: + age: 40 + is_adult: true + in_work: true + parent2: + age: 38 + is_adult: true + in_work: false + child: + age: 4 + is_child: true + benunits: + benunit: + members: [parent1, parent2, child] + output: + extended_childcare_entitlement_work_condition: [0, 0, 0] + +- name: Couple with children, one working one disabled - eligible + period: 2025 + input: + people: + parent1: + age: 40 + is_adult: true + in_work: true + parent2: + age: 38 + is_adult: true + in_work: false + incapacity_benefit: 100 + child: + age: 4 + is_child: true + benunits: + benunit: + members: [parent1, parent2, child] + output: + extended_childcare_entitlement_work_condition: [1, 1, 0] + +- name: Couple without children both working - ineligible + period: 2025 + input: + people: + person1: + age: 40 + is_adult: true + in_work: true + person2: + age: 38 + is_adult: true + in_work: true + benunits: + benunit: + members: [person1, person2] + output: + extended_childcare_entitlement_work_condition: [0, 0] \ No newline at end of file diff --git a/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/conditions/extended_childcare_entitlement_meets_income_requirements.py b/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/conditions/extended_childcare_entitlement_meets_income_requirements.py new file mode 100644 index 000000000..30cd5fa70 --- /dev/null +++ b/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/conditions/extended_childcare_entitlement_meets_income_requirements.py @@ -0,0 +1,35 @@ +from policyengine_uk.model_api import * + + +class extended_childcare_entitlement_meets_income_requirements(Variable): + value_type = bool + entity = Person + label = "Income eligible for the extended childcare entitlement" + documentation = "Whether this person meets the income requirements for extended childcare entitlement based on age and income thresholds" + definition_period = YEAR + + # Legislation: https://www.legislation.gov.uk/ukdsi/2015/9780111127063 , part 9 and 10 + # Also, you can check here: https://www.gov.uk/tax-free-childcare + + def formula(person, period, parameters): + p = parameters(period).gov.dfe.extended_childcare_entitlement + + # Calculate eligible income by summing countable sources + yearly_eligible_income = add( + person, period, p.income.countable_sources + ) + quarterly_income = yearly_eligible_income / 4 + + # Get minimum wage rate using existing variable + min_wage_rate = person("minimum_wage", period) + + # Calculate required threshold (weekly hours * 13 weeks (a quarter) * minimum wage) + # Reference for the quarterly logic: part 9.3 in https://www.legislation.gov.uk/uksi/2015/448/regulation/9 + required_threshold = min_wage_rate * p.minimum_weekly_hours * 13 + + # Get adjusted net income and check against max threshold + ani = person("adjusted_net_income", period) + + return (quarterly_income > required_threshold) & ( + ani < p.income.maximum_adjusted_income + ) diff --git a/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/conditions/extended_childcare_entitlement_work_condition.py b/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/conditions/extended_childcare_entitlement_work_condition.py new file mode 100644 index 000000000..9cea15b67 --- /dev/null +++ b/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/conditions/extended_childcare_entitlement_work_condition.py @@ -0,0 +1,59 @@ +from policyengine_uk.model_api import * + + +class extended_childcare_entitlement_work_condition(Variable): + value_type = bool + entity = Person + label = "Work conditions for extended childcare entitlement" + documentation = "Whether the person/couple meets work requirements for extended childcare entitlement" + definition_period = YEAR + + def formula(person, period, parameters): + benunit = person.benunit + is_child = person("is_child", period) + in_work = person("in_work", period) + is_parent = person("is_parent", period) + + # Get disability status + p = parameters( + period + ).gov.dwp.pension_credit.guarantee_credit.child.disability + + eligible_based_on_disability = ( + add( + person, + period, + p.eligibility + p.severe.eligibility, + ) + > 0 + ) | (person("incapacity_benefit", period) > 0) + + # Count parents in benefit unit + parent_count = add(benunit, period, ["is_parent"]) + has_children = benunit.any(benunit.members("is_child", period)) + + # Eligibility conditions + lone_parent_eligible = ( + (parent_count == 1) & has_children & in_work & is_parent + ) + + # Break out the complex nested conditions + all_parents_working = benunit.all(where(is_parent, in_work, True)) + some_parents_working = benunit.any(in_work & is_parent) + some_parents_disability_eligible = benunit.any( + eligible_based_on_disability & is_parent + ) + + # Create a separate work condition + couple_work_condition = all_parents_working | ( + some_parents_working & some_parents_disability_eligible + ) + + couple_eligible = ( + (parent_count == 2) + & has_children + & is_parent + & couple_work_condition + ) + + return where(is_child, False, lone_parent_eligible | couple_eligible) diff --git a/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement.py b/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement.py new file mode 100644 index 000000000..7ef4ef384 --- /dev/null +++ b/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement.py @@ -0,0 +1,25 @@ +from policyengine_uk.model_api import * + + +class extended_childcare_entitlement(Variable): + value_type = float + entity = BenUnit + label = "Annual extended childcare entitlement expenses" + documentation = "Annual expenses for extended childcare entitlement calculated as hours per week * hourly rate * weeks per year" + definition_period = YEAR + unit = GBP + + def formula(benunit, period, parameters): + # Get weekly hours + weekly_hours_per_child = benunit.members( + "extended_childcare_entitlement_hours", period + ) + + # Get expense rate from parameters + p = parameters(period).gov.dfe.extended_childcare_entitlement + + age = benunit.members("age", period) + subsidy_per_child = weekly_hours_per_child * p.expense_rate.calc(age) + + # Calculate annual expenses + return benunit.sum(subsidy_per_child) * p.weeks_per_year diff --git a/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_hours.py b/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_hours.py new file mode 100644 index 000000000..e4803ff0c --- /dev/null +++ b/policyengine_uk/variables/gov/dfe/extended_childcare_entitlement/extended_childcare_entitlement_hours.py @@ -0,0 +1,35 @@ +from policyengine_uk.model_api import * + + +class extended_childcare_entitlement_hours(Variable): + value_type = float + entity = Person + label = "Hours of extended childcare entitlement" + documentation = "Number of hours of extended childcare for this child entitlement based on eligibility conditions" + definition_period = YEAR + + def formula(person, period, parameters): + benunit = person.benunit + # Get parameters + p = parameters( + period + ).gov.dfe.extended_childcare_entitlement.childcare_entitlement_hours + + # Check income condition - must be true for all family members (except children) + meets_income_condition = benunit.all( + benunit.members( + "extended_childcare_entitlement_meets_income_requirements", + period, + ) + | benunit.members("is_child", period) + ) + + # Check work condition + work_eligible = ( + benunit("extended_childcare_entitlement_work_condition", period) + > 0 + ) + + hours_per_child = p.calc(person("age", period)) + + return hours_per_child * meets_income_condition * work_eligible diff --git a/policyengine_uk/variables/household/demographic/person.py b/policyengine_uk/variables/household/demographic/person.py index b1c8dbcd6..64281f599 100644 --- a/policyengine_uk/variables/household/demographic/person.py +++ b/policyengine_uk/variables/household/demographic/person.py @@ -375,3 +375,40 @@ class is_older_child(Variable): def formula(person, period, parameters): age = person("age", period) return (age >= 14) & (age < 18) + + +class is_parent(Variable): + value_type = bool + entity = Person + label = "Whether this person is a parent in the benefit unit" + definition_period = YEAR + + def formula(person, period, parameters): + benunit = person.benunit + age = person("age", period) + has_children = benunit.any(benunit.members("is_child", period)) + + # Check if person is among two oldest + benunit_ages = benunit.members("age", period) + first_highest = benunit.max(benunit_ages) + is_among_two_oldest = (age == first_highest) | ( + age + == benunit.max( + where(benunit_ages < first_highest, benunit_ages, -inf) + ) + ) + + # Count adults to determine if lone parent or couple + adult_count = add(benunit, period, ["is_adult"]) + + # For lone parents, only eldest is parent + is_lone_parent = ( + (adult_count == 1) & has_children & (age == first_highest) + ) + + # For couples with children, two eldest are parents + is_couple_parent = ( + (adult_count == 2) & has_children & is_among_two_oldest + ) + + return is_lone_parent | is_couple_parent diff --git a/policyengine_uk/variables/household/income/benefit.py b/policyengine_uk/variables/household/income/benefit.py index 6584155b9..4c96c610b 100644 --- a/policyengine_uk/variables/household/income/benefit.py +++ b/policyengine_uk/variables/household/income/benefit.py @@ -48,6 +48,7 @@ class household_benefits(Variable): "education_budget_change", "other_public_spending_budget_change", "tax_free_childcare", + "extended_childcare_entitlement", ] def formula(household, period, parameters):