From e37efb746954117a9ec0982e01fd9b5dcb2ec263 Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Wed, 21 Oct 2026 14:28:47 -0400 Subject: [PATCH] Use UC earned income before the work allowance in targeted childcare and local CTR Part of #1986 (items 2 and 3; item 1, the benefit cap earnings exception, is in #1820 and #2536). SI 2014/2147 reg 1(3)(a) tests targeted (two-year-old) childcare on earned income "for the purposes of Chapter 2 of Part 6" of the UC Regulations 2013, and the Kingston upon Thames, Merton, Newham and Westminster Council Tax Reduction schemes take the Secretary of State's calculation of income (Default Scheme para 37). Neither deducts the work allowance, which is a step in the award (reg 22). Both read the new uc_earned_income_before_work_allowance; uc_earned_income is that less the work allowance, as before. Ported from #1999. Co-Authored-By: Claude Opus 5.5 --- changelog.d/1986.fixed.md | 1 + .../dfe/targeted-childcare-entitlement.ipynb | 2 +- ...ia_for_targeted_childcare_entitlement.yaml | 247 ++++++++++++++++++ .../council_tax_reduction.yaml | 152 ++++++++++- ...income_before_work_allowance_properties.py | 177 +++++++++++++ ...eria_for_targeted_childcare_entitlement.py | 26 +- .../income/uc_earned_income.py | 10 +- .../uc_earned_income_before_work_allowance.py | 43 +++ .../council_tax_reduction/_legacy.py | 13 +- 9 files changed, 654 insertions(+), 17 deletions(-) create mode 100644 changelog.d/1986.fixed.md create mode 100644 policyengine_uk/tests/policy/baseline/gov/dfe/targeted_childcare_entitlement/meets_universal_credit_criteria_for_targeted_childcare_entitlement.yaml create mode 100644 policyengine_uk/tests/test_uc_earned_income_before_work_allowance_properties.py create mode 100644 policyengine_uk/variables/gov/dwp/universal_credit/income/uc_earned_income_before_work_allowance.py diff --git a/changelog.d/1986.fixed.md b/changelog.d/1986.fixed.md new file mode 100644 index 0000000000..3d2f2dd6b0 --- /dev/null +++ b/changelog.d/1986.fixed.md @@ -0,0 +1 @@ +- Targeted (two-year-old) childcare and the Kingston upon Thames, Merton, Newham and Westminster Council Tax Reduction schemes now read Universal Credit earned income before the work allowance (`uc_earned_income_before_work_allowance`), as SI 2014/2147 reg 1(3)(a) and the schemes require. diff --git a/docs/book/programs/gov/dfe/targeted-childcare-entitlement.ipynb b/docs/book/programs/gov/dfe/targeted-childcare-entitlement.ipynb index 3b41a8f8a5..0c1c2cb9d4 100644 --- a/docs/book/programs/gov/dfe/targeted-childcare-entitlement.ipynb +++ b/docs/book/programs/gov/dfe/targeted-childcare-entitlement.ipynb @@ -28,7 +28,7 @@ "\n", "#### Eligibility conditions\n", "Families may also qualify based on income thresholds if they receive:\n", - "* **Universal Credit** with household earned income of **£15,400 or less** per year\n", + "* **Universal Credit** with earned income of **£15,400 or less** per year (combined for a couple), as Universal Credit calculates it: earnings after the person's own tax, National Insurance and pension contributions, and before the work allowance (`uc_earned_income_before_work_allowance`)\n", "* **Tax Credits** (Working Tax Credit or Child Tax Credit) with household income of **£16,190 or less** per year\n", "\n", "#### Geographic scope\n", diff --git a/policyengine_uk/tests/policy/baseline/gov/dfe/targeted_childcare_entitlement/meets_universal_credit_criteria_for_targeted_childcare_entitlement.yaml b/policyengine_uk/tests/policy/baseline/gov/dfe/targeted_childcare_entitlement/meets_universal_credit_criteria_for_targeted_childcare_entitlement.yaml new file mode 100644 index 0000000000..85809ad22e --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dfe/targeted_childcare_entitlement/meets_universal_credit_criteria_for_targeted_childcare_entitlement.yaml @@ -0,0 +1,247 @@ +# Targeted (two-year-old) childcare, Universal Credit route. Local Authority +# (Duty to Secure Early Years Provision Free of Charge) Regulations 2014 +# reg. 1(2), definition of "eligible child" (a)(vii), and reg. 1(3)-(4): +# https://www.legislation.gov.uk/uksi/2014/2147/regulation/1 +# +# The parent must be entitled to universal credit with earned income not +# exceeding the applicable amount. Reg. 1(3)(a): "earned income" means income +# for the purposes of Chapter 2 of Part 6 of the UC Regulations 2013, which +# is net of the person's own tax, NI and pension contributions and comes +# before the work allowance (reg. 22, Part 3). Reg. 1(3)(c) reads the limit +# against a couple's combined income. The limit here is 15,400 a year. +# In 2026-27 the personal allowance and the primary threshold are 12,570. + +- name: "#1986 counterexample: earnings of 17,000 exceed the limit" + # Tax (17,000 - 12,570) x 20% = 886; NI (17,000 - 12,570) x 8% = 354.40; + # earned income 15,759.60 > 15,400. After the 5,124 work allowance it + # would be 10,635.60, which is how the model wrongly passed it before. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 17_000 + child: + age: 2 + benunits: + benunit: + members: [parent, child] + would_claim_uc: true + households: + household: + members: [parent, child] + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + output: + uc_earned_income_before_work_allowance: 15_759.60 + uc_earned_income: 10_635.60 + meets_universal_credit_criteria_for_targeted_childcare_entitlement: false + +- name: Earned income just under the limit meets the criterion + # Tax (16,500 - 12,570) x 20% = 786; NI 3,930 x 8% = 314.40; earned income + # 15,399.60 <= 15,400. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 16_500 + child: + age: 2 + benunits: + benunit: + members: [parent, child] + would_claim_uc: true + households: + household: + members: [parent, child] + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + output: + uc_earned_income_before_work_allowance: 15_399.60 + meets_universal_credit_criteria_for_targeted_childcare_entitlement: true + +- name: Earned income just over the limit does not + # Tax 3,931 x 20% = 786.20; NI 3,931 x 8% = 314.48; earned income + # 15,400.32 > 15,400. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 16_501 + child: + age: 2 + benunits: + benunit: + members: [parent, child] + would_claim_uc: true + households: + household: + members: [parent, child] + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + output: + uc_earned_income_before_work_allowance: 15_400.32 + meets_universal_credit_criteria_for_targeted_childcare_entitlement: false + +- name: A couple's combined earned income is tested + # Reg. 1(3)(c): 8,000 + 8,000 = 16,000 > 15,400, although each partner is + # under the limit alone. + period: 2026 + input: + people: + parent1: + age: 30 + employment_income: 8_000 + parent2: + age: 30 + employment_income: 8_000 + child: + age: 2 + benunits: + benunit: + members: [parent1, parent2, child] + would_claim_uc: true + households: + household: + members: [parent1, parent2, child] + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + output: + uc_earned_income_before_work_allowance: 16_000 + meets_universal_credit_criteria_for_targeted_childcare_entitlement: false + +- name: Combined earned income exactly at the limit meets the criterion + # "not exceeding": 7,700 + 7,700 = 15,400 <= 15,400. Each is under the + # personal allowance and primary threshold, so no tax or NI. + period: 2026 + input: + people: + parent1: + age: 30 + employment_income: 7_700 + parent2: + age: 30 + employment_income: 7_700 + child: + age: 2 + benunits: + benunit: + members: [parent1, parent2, child] + would_claim_uc: true + households: + household: + members: [parent1, parent2, child] + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + output: + uc_earned_income_before_work_allowance: 15_400 + meets_universal_credit_criteria_for_targeted_childcare_entitlement: true + +- name: Income treated as earned under the minimum income floor counts + # Reg. 1(3)(a) uses UC Part 6 Chapter 2, which includes reg. 62. The + # partner who is not the child's carer has profits of 3,000 and is subject + # to the floor (35 hours at the national living wage), so the couple's + # earned income is far above 15,400, although actual earnings are 3,000. + # The couple nominate the other partner as the child's responsible carer + # (reg. 86); the responsible carer of a child under 3 is not subject to + # the floor (uc_work_related_group_apart_from_earnings). + period: 2026 + input: + people: + self_employed_partner: + age: 30 + self_employment_income: 3_000 + uc_is_in_startup_period: false + uc_is_responsible_carer: false + carer: + age: 30 + uc_is_responsible_carer: true + child: + age: 2 + benunits: + benunit: + members: [self_employed_partner, carer, child] + would_claim_uc: true + households: + household: + members: [self_employed_partner, carer, child] + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + output: + uc_mif_applies: [true, false, false] + meets_universal_credit_criteria_for_targeted_childcare_entitlement: false + +- name: Without pension contributions, earnings of 16,800 exceed the limit + # Tax 4,230 x 20% = 846; NI 4,230 x 8% = 338.40; earned income 15,615.60. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 16_800 + child: + age: 2 + benunits: + benunit: + members: [parent, child] + would_claim_uc: true + households: + household: + members: [parent, child] + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + output: + uc_earned_income_before_work_allowance: 15_615.60 + meets_universal_credit_criteria_for_targeted_childcare_entitlement: false + +- name: Pension contributions come off earned income for the limit + # Reg. 55(5)(a): the 1,000 contribution brings earned income to at most + # 16,800 - 1,000 - 338.40 - 646 = 14,815.60 (with tax relief at source), + # under 15,400. + period: 2026 + input: + people: + parent: + age: 30 + employment_income: 16_800 + employee_pension_contributions: 1_000 + child: + age: 2 + benunits: + benunit: + members: [parent, child] + would_claim_uc: true + households: + household: + members: [parent, child] + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + output: + meets_universal_credit_criteria_for_targeted_childcare_entitlement: true + +- name: No Universal Credit entitlement, no Universal Credit route + period: 2026 + input: + people: + parent: + age: 30 + employment_income: 5_000 + child: + age: 2 + benunits: + benunit: + members: [parent, child] + would_claim_uc: false + households: + household: + members: [parent, child] + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + output: + meets_universal_credit_criteria_for_targeted_childcare_entitlement: false diff --git a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction.yaml b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction.yaml index daaec21e26..6bffa6850d 100644 --- a/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction.yaml +++ b/policyengine_uk/tests/policy/baseline/gov/local_authorities/council_tax_reduction/council_tax_reduction.yaml @@ -215,7 +215,7 @@ claims_all_entitled_benefits: true uc_maximum_amount: 5_000 uc_income_reduction: 2_500 - uc_earned_income: 6_000 + uc_earned_income_before_work_allowance: 6_000 uc_unearned_income: 0 households: household: @@ -228,6 +228,43 @@ universal_credit: 2_500 council_tax_reduction: 1_100 +- name: "Merton UC claimant's income counts earnings before the work allowance" + # #1986. Single parent, child aged 6, earning 12,000 (under the personal + # allowance and primary threshold, so earned income is 12,000), council rent, + # council tax 1,800. The award has a housing costs element, so the work + # allowance is 427 x 12 = 5,124 and UC = maximum - 55% x (12,000 - 5,124) = + # maximum - 3,781.80. Income (scheme para. 37) is earned income plus the + # award: 12,000 + maximum - 3,781.80, which exceeds the applicable amount + # (the maximum, para. 28) by 8,218.20. CTR = 1,800 - 20% x 8,218.20 = + # 156.36. Deducting the work allowance from income would have given 1,181.16. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 12_000 + child: + age: 6 + benunits: + benunit: + members: [parent, child] + would_claim_uc: true + claims_all_entitled_benefits: true + households: + household: + members: [parent, child] + country: ENGLAND + region: LONDON + local_authority: MERTON + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + council_tax: 1_800 + savings: 0 + output: + uc_earned_income_before_work_allowance: 12_000 + council_tax_reduction: 156.36 + - name: Merton UC claimant uses UC-reported capital period: 2026 absolute_error_margin: 0.01 @@ -371,7 +408,7 @@ claims_all_entitled_benefits: true uc_maximum_amount: 5_000 uc_income_reduction: 2_500 - uc_earned_income: 6_000 + uc_earned_income_before_work_allowance: 6_000 uc_unearned_income: 0 households: household: @@ -384,6 +421,43 @@ universal_credit: 2_500 council_tax_reduction: 1_100 +- name: "Kingston upon Thames UC claimant's income counts earnings before the work allowance" + # #1986. Single parent, child aged 6, earning 12,000 (under the personal + # allowance and primary threshold, so earned income is 12,000), council rent, + # council tax 1,800. The award has a housing costs element, so the work + # allowance is 427 x 12 = 5,124 and UC = maximum - 55% x (12,000 - 5,124) = + # maximum - 3,781.80. Income (scheme para. 37) is earned income plus the + # award: 12,000 + maximum - 3,781.80, which exceeds the applicable amount + # (the maximum, para. 28) by 8,218.20. CTR = 1,800 - 20% x 8,218.20 = + # 156.36. Deducting the work allowance from income would have given 1,181.16. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 12_000 + child: + age: 6 + benunits: + benunit: + members: [parent, child] + would_claim_uc: true + claims_all_entitled_benefits: true + households: + household: + members: [parent, child] + country: ENGLAND + region: LONDON + local_authority: KINGSTON_UPON_THAMES + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + council_tax: 1_800 + savings: 0 + output: + uc_earned_income_before_work_allowance: 12_000 + council_tax_reduction: 156.36 + - name: Kingston upon Thames UC claimant uses UC-reported capital period: 2026 absolute_error_margin: 0.01 @@ -542,6 +616,41 @@ output: council_tax_reduction: 867.92 +- name: Newham UC claimant's income counts earnings before the work allowance + # #1986. Single parent, child aged 6, earning 4,000, council rent, council + # tax 1,800. Earnings are under the 5,124 work allowance, so UC is the + # maximum. Income is 4,000 + maximum, 4,000 over the applicable amount, so + # CTR = 70% x 1,800 - 25% x 4,000 = 260. Deducting the work allowance from + # income would have left no excess and given 1,260. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 4_000 + child: + age: 6 + benunits: + benunit: + members: [parent, child] + would_claim_uc: true + claims_all_entitled_benefits: true + households: + household: + members: [parent, child] + country: ENGLAND + region: LONDON + local_authority: NEWHAM + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + council_tax: 1_800 + savings: 0 + output: + uc_earned_income_before_work_allowance: 4_000 + uc_earned_income: 0 + council_tax_reduction: 260 + - name: Newham UC claimant uses UC-reported capital against the local limit period: 2026 absolute_error_margin: 0.01 @@ -686,7 +795,7 @@ claims_all_entitled_benefits: true uc_maximum_amount: 5_000 uc_income_reduction: 2_500 - uc_earned_income: 6_000 + uc_earned_income_before_work_allowance: 6_000 uc_unearned_income: 0 households: household: @@ -699,6 +808,43 @@ universal_credit: 2_500 council_tax_reduction: 1_100 +- name: "Westminster UC claimant's income counts earnings before the work allowance" + # #1986. Single parent, child aged 6, earning 12,000 (under the personal + # allowance and primary threshold, so earned income is 12,000), council rent, + # council tax 1,800. The award has a housing costs element, so the work + # allowance is 427 x 12 = 5,124 and UC = maximum - 55% x (12,000 - 5,124) = + # maximum - 3,781.80. Income (scheme para. 37) is earned income plus the + # award: 12,000 + maximum - 3,781.80, which exceeds the applicable amount + # (the maximum, para. 28) by 8,218.20. CTR = 1,800 - 20% x 8,218.20 = + # 156.36. Deducting the work allowance from income would have given 1,181.16. + period: 2026 + absolute_error_margin: 0.01 + input: + people: + parent: + age: 30 + employment_income: 12_000 + child: + age: 6 + benunits: + benunit: + members: [parent, child] + would_claim_uc: true + claims_all_entitled_benefits: true + households: + household: + members: [parent, child] + country: ENGLAND + region: LONDON + local_authority: WESTMINSTER + tenure_type: RENT_FROM_COUNCIL + rent: 9_600 + council_tax: 1_800 + savings: 0 + output: + uc_earned_income_before_work_allowance: 12_000 + council_tax_reduction: 156.36 + - name: Westminster UC claimant uses UC-reported capital period: 2026 absolute_error_margin: 0.01 diff --git a/policyengine_uk/tests/test_uc_earned_income_before_work_allowance_properties.py b/policyengine_uk/tests/test_uc_earned_income_before_work_allowance_properties.py new file mode 100644 index 0000000000..e40c9a449a --- /dev/null +++ b/policyengine_uk/tests/test_uc_earned_income_before_work_allowance_properties.py @@ -0,0 +1,177 @@ +"""Property-based tests for Universal Credit earned income before the work +allowance, and the two rules that read it (#1986, items 2 and 3). + +Targeted (two-year-old) childcare tests earned income "for the purposes of +Chapter 2 of Part 6 of the Universal Credit Regulations 2013" (SI 2014/2147 +reg. 1(3)(a)), and the Kingston upon Thames, Merton, Newham and Westminster +Council Tax Reduction schemes take the Secretary of State's calculation of the +claimant's income (Default Scheme para. 37). Neither subtracts the work +allowance, which is a step in the award (UC Regs 2013 reg. 22(1)(b)). + +Invariants, for single people and couples with a child aged 2, earnings, +self-employment and pension contributions, in 2025, 2026 and 2027: + +1. Differential against the award: uc_earned_income equals + max(0, uc_earned_income_before_work_allowance - uc_work_allowance), so the + award's earned income is unchanged by this PR. +2. Differential against the formula this replaced: the childcare criterion + now implies the criterion main used (earned income after the work + allowance at or below the limit), because earned income before the work + allowance is never lower. +3. Monotone: more employment income for one adult never turns the childcare + criterion on. +""" + +import numpy as np +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation + +PROPERTY_SETTINGS = settings( + max_examples=10, + deadline=None, + derandomize=True, + suppress_health_check=[HealthCheck.too_slow, HealthCheck.data_too_large], +) +YEARS = [2025, 2026, 2027] +TENURES = ["RENT_FROM_COUNCIL", "RENT_FROM_HA", "RENT_PRIVATELY"] +VARIABLES = [ + "uc_earned_income", + "uc_earned_income_before_work_allowance", + "uc_work_allowance", + "universal_credit", + "meets_universal_credit_criteria_for_targeted_childcare_entitlement", +] + + +@st.composite +def adult(draw): + return dict( + age=draw(st.integers(18, 60)), + employment_income=draw( + st.one_of(st.just(0.0), st.floats(0, 40_000), st.floats(14_000, 19_000)) + ), + self_employment_income=draw(st.one_of(st.just(0.0), st.floats(0, 10_000))), + pension_contributions=draw(st.one_of(st.just(0.0), st.floats(0, 2_000))), + ) + + +@st.composite +def families(draw): + return dict( + adults=[draw(adult()) for _ in range(draw(st.sampled_from([1, 2])))], + tenure=draw(st.sampled_from(TENURES)), + rent=draw(st.floats(4_000, 20_000)), + ) + + +units = st.lists(families(), min_size=1, max_size=6) + + +def situation(units, year, bump=None): + """One simulation holding every family. ``bump`` is (family, adult, + amount) of extra employment income.""" + people, benunits, households = {}, {}, {} + for i, unit in enumerate(units): + names = [] + for j, a in enumerate(unit["adults"]): + person = {k: {year: v} for k, v in a.items()} + # Roles are given, not inferred; neither adult is under the + # minimum income floor (a start-up period), so the floor does + # not move earned income here. + person["is_claimant_or_partner"] = {year: True} + person["uc_is_in_startup_period"] = {year: True} + if bump is not None and bump[:2] == (i, j): + person["employment_income"] = {year: a["employment_income"] + bump[2]} + people[f"p{i}_{j}"] = person + names.append(f"p{i}_{j}") + people[f"c{i}"] = {"age": {year: 2}, "is_claimant_or_partner": {year: False}} + names.append(f"c{i}") + benunits[f"b{i}"] = {"members": names, "would_claim_uc": {year: True}} + households[f"h{i}"] = { + "members": names, + "tenure_type": {year: unit["tenure"]}, + "rent": {year: unit["rent"]}, + "region": {year: "NORTH_EAST"}, + } + return {"people": people, "benunits": benunits, "households": households} + + +def calculate(units, year, **kwargs): + sim = Simulation(situation=situation(units, year, **kwargs)) + values = {v: np.asarray(sim.calculate(v, year)) for v in VARIABLES} + values["limit"] = float( + sim.tax_benefit_system.parameters( + f"{year}-01-01" + ).gov.dfe.targeted_childcare_entitlement.income_limit.universal_credit + ) + return values + + +@PROPERTY_SETTINGS +@given(units=units, year=st.sampled_from(YEARS)) +def test_award_earned_income_is_before_work_allowance_less_the_allowance(units, year): + v = calculate(units, year) + expected = np.maximum( + 0, v["uc_earned_income_before_work_allowance"] - v["uc_work_allowance"] + ) + np.testing.assert_allclose(v["uc_earned_income"], expected, atol=0.01) + assert ( + v["uc_earned_income_before_work_allowance"] >= v["uc_earned_income"] - 0.01 + ).all() + + +@PROPERTY_SETTINGS +@given(units=units, year=st.sampled_from(YEARS)) +def test_childcare_criterion_implies_the_old_after_allowance_test(units, year): + v = calculate(units, year) + old = (v["universal_credit"] > 0) & (v["uc_earned_income"] <= v["limit"]) + new = v["meets_universal_credit_criteria_for_targeted_childcare_entitlement"] + assert not (new & ~old).any(), units + # And the new test is exactly the regulation's: UC in payment and earned + # income before the work allowance at or below the limit. + expected = (v["universal_credit"] > 0) & ( + v["uc_earned_income_before_work_allowance"] <= v["limit"] + ) + assert (new == expected).all(), units + + +@PROPERTY_SETTINGS +@given( + units=units, + year=st.sampled_from(YEARS), + data=st.data(), +) +def test_more_pay_never_turns_the_childcare_criterion_on(units, year, data): + i = data.draw(st.integers(0, len(units) - 1)) + j = data.draw(st.integers(0, len(units[i]["adults"]) - 1)) + amount = data.draw(st.floats(100, 5_000)) + before = calculate(units, year) + after = calculate(units, year, bump=(i, j, amount)) + criterion = "meets_universal_credit_criteria_for_targeted_childcare_entitlement" + assert not (after[criterion] & ~before[criterion]).any(), (units, i, j, amount) + + +def test_crossing_the_limit_by_one_pound_of_pay(): + """A fixed crossing, so the monotone property is not vacuous: a single + parent aged 30 earning £16,500 (£15,399.60 after tax and NI) meets the + 2026-27 criterion, and £16,501 (£15,400.32) does not.""" + family = dict( + adults=[ + dict( + age=30, + employment_income=16_500.0, + self_employment_income=0.0, + pension_contributions=0.0, + ) + ], + tenure="RENT_FROM_COUNCIL", + rent=9_600.0, + ) + criterion = "meets_universal_credit_criteria_for_targeted_childcare_entitlement" + under = calculate([family], 2026) + over = calculate([family], 2026, bump=(0, 0, 1.0)) + assert under["universal_credit"][0] > 0 and over["universal_credit"][0] > 0 + assert abs(under["uc_earned_income_before_work_allowance"][0] - 15_399.60) < 0.01 + assert under[criterion][0] and not over[criterion][0] diff --git a/policyengine_uk/variables/gov/dfe/targeted_childcare_entitlement/meets_universal_credit_criteria_for_targeted_childcare_entitlement.py b/policyengine_uk/variables/gov/dfe/targeted_childcare_entitlement/meets_universal_credit_criteria_for_targeted_childcare_entitlement.py index 52a75c07be..b8e4c7f41e 100644 --- a/policyengine_uk/variables/gov/dfe/targeted_childcare_entitlement/meets_universal_credit_criteria_for_targeted_childcare_entitlement.py +++ b/policyengine_uk/variables/gov/dfe/targeted_childcare_entitlement/meets_universal_credit_criteria_for_targeted_childcare_entitlement.py @@ -5,7 +5,19 @@ class meets_universal_credit_criteria_for_targeted_childcare_entitlement(Variabl value_type = bool entity = BenUnit label = "meets Universal Credit criteria for targeted childcare entitlement" + documentation = ( + "Whether the parents are entitled to Universal Credit with earned " + "income, as Universal Credit calculates it and before the work " + "allowance, not exceeding the limit." + ) definition_period = YEAR + reference = dict( + title=( + "Local Authority (Duty to Secure Early Years Provision Free of " + "Charge) Regulations 2014 reg. 1(2), (3) and (4)" + ), + href="https://www.legislation.gov.uk/uksi/2014/2147/regulation/1", + ) def formula(benunit, period, parameters): p = parameters(period).gov.dfe.targeted_childcare_entitlement @@ -13,11 +25,13 @@ def formula(benunit, period, parameters): # Check if receiving Universal Credit uc = benunit("universal_credit", period) - # For Universal Credit recipients, we must use the definition of "earned income" - # The Local Authority (Duty to Secure Early Years Provision Free of Charge) (Amendment) Regulations 2018 - part 2.c - # https://www.legislation.gov.uk/uksi/2018/146/made - - # UC-specific earned income as specified by 2(c)(3)(a) of the Regulations - earned_income = benunit("uc_earned_income", period) + # Reg. 1(3)(a): "earned income" means income for the purposes of + # Chapter 2 of Part 6 of the Universal Credit Regulations 2013, and + # reg. 1(3)(c) reads the limit against a couple's combined income. + # Chapter 2 is each person's earnings after their own tax, National + # Insurance and pension contributions, with the minimum income floor + # (reg. 62). The work allowance is in reg. 22 (Part 3, the award), so + # it is not deducted here. + earned_income = benunit("uc_earned_income_before_work_allowance", period) return (uc > 0) & (earned_income <= p.income_limit.universal_credit) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_earned_income.py b/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_earned_income.py index 9a69a049ac..640a2b2cac 100644 --- a/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_earned_income.py +++ b/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_earned_income.py @@ -28,11 +28,9 @@ def formula(benunit, period, parameters): # Reg. 22(1)(b) deducts "the claimant's earned income (or, in the case # of joint claimants, their combined earned income)". Each claimant's # earned income is net of their own deductions (reg. 55(5), reg. - # 57(2)); the work allowance then comes off the combined earned - # income before the taper. - claimants = benunit.members("is_uc_assessed_claimant", period) - earned_income = add_for_members( - benunit, period, ["uc_individual_earned_income"], claimants - ) + # 57(2)), summed over the claimants + # (uc_earned_income_before_work_allowance); the work allowance then + # comes off the combined earned income before the taper. + earned_income = benunit("uc_earned_income_before_work_allowance", period) work_allowance = benunit("uc_work_allowance", period) return max_(0, earned_income - work_allowance) diff --git a/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_earned_income_before_work_allowance.py b/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_earned_income_before_work_allowance.py new file mode 100644 index 0000000000..2ae3f44cbf --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/universal_credit/income/uc_earned_income_before_work_allowance.py @@ -0,0 +1,43 @@ +from policyengine_uk.model_api import * + + +class uc_earned_income_before_work_allowance(Variable): + value_type = float + entity = BenUnit + label = "Universal Credit earned income before the work allowance" + documentation = ( + "The claimant's earned income, or joint claimants' combined earned " + "income, as Universal Credit calculates it (UC Regs 2013 Part 6 " + "Chapter 2): each claimant's earnings less their own pension " + "contributions, income tax and National Insurance, or the amount the " + "minimum income floor treats them as having. Earnings of a child, a " + "qualifying young person or anyone else in the benefit unit who is " + "not a claimant do not count. The work allowance, which belongs to " + "the award calculation in reg. 22, is not deducted. Rules that refer " + "to the Universal Credit calculation of earned income use this amount." + ) + definition_period = YEAR + unit = GBP + reference = [ + dict( + title="Universal Credit Regulations 2013 reg. 52", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/52", + ), + dict( + title="Universal Credit Regulations 2013 reg. 22(1)(b)", + href="https://www.legislation.gov.uk/uksi/2013/376/regulation/22", + ), + dict( + title="Welfare Reform Act 2012 s. 8(3) and (4)", + href="https://www.legislation.gov.uk/ukpga/2012/5/section/8", + ), + ] + + def formula(benunit, period, parameters): + # Reg. 22(1)(b) takes "the claimant's earned income (or, in the case + # of joint claimants, their combined earned income)", so only the + # claimants' earned income counts. + claimants = benunit.members("is_uc_assessed_claimant", period) + return add_for_members( + benunit, period, ["uc_individual_earned_income"], claimants + ) diff --git a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_legacy.py b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_legacy.py index 52524b2828..5d96830c62 100644 --- a/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_legacy.py +++ b/policyengine_uk/variables/gov/local_authorities/council_tax_reduction/_legacy.py @@ -36,8 +36,19 @@ def legacy_council_tax_reduction( universal_credit = benunit("universal_credit", period) has_uc_award = applicant_has_universal_credit(benunit, period) uc_applicable_amount = benunit("uc_maximum_amount", period) + # The schemes use "the calculation or estimate of the amount of the + # income of the applicant ... made by the Secretary of State for the + # purpose of determining the award of universal credit", plus the award + # (Council Tax Reduction Schemes (Default Scheme) (England) Regulations + # 2012, Sch. para. 37(1) and (3)(a); Kingston upon Thames and Merton + # reproduce it, Newham refers to the same calculation, and Westminster's + # scheme is based on the Default Scheme). That income is + # earned income after tax, National Insurance and pension contributions + # and unearned income. The work allowance and taper are steps in the + # award (UC Regs 2013 reg. 22), not in the income, so the work allowance + # is not deducted here. uc_applicable_income = ( - benunit("uc_earned_income", period) + benunit("uc_earned_income_before_work_allowance", period) + benunit("uc_unearned_income", period) + universal_credit )