diff --git a/changelog.d/2309.fixed.md b/changelog.d/2309.fixed.md new file mode 100644 index 0000000000..db731a062c --- /dev/null +++ b/changelog.d/2309.fixed.md @@ -0,0 +1 @@ +- Stop paying Carer's Allowance to a carer whose net earnings exceed the earnings limit (£204 a week from April 2026). diff --git a/policyengine_uk/parameters/gov/dwp/carers_allowance/earnings_limit.yaml b/policyengine_uk/parameters/gov/dwp/carers_allowance/earnings_limit.yaml new file mode 100644 index 0000000000..22c25451e7 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/carers_allowance/earnings_limit.yaml @@ -0,0 +1,62 @@ +description: Weekly earnings above which a person is treated as gainfully employed and so is not entitled to Carer's Allowance. The test uses net earnings in the preceding week. +metadata: + economy: false + period: week + unit: currency-GBP + label: Carer's Allowance earnings limit + reference: + - title: The Social Security (Invalid Care Allowance) Regulations 1976 reg. 8(1) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8 + - title: Social Security Contributions and Benefits Act 1992 s. 70(1)(b) + href: https://www.legislation.gov.uk/ukpga/1992/4/section/70 +values: + 2015-04-06: + value: 110 + reference: + - title: The Social Security (Invalid Care Allowance) (Amendment) Regulations 2015 (SI 2015/162) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8/2015-04-06 + 2017-04-10: + value: 116 + reference: + - title: The Social Security (Invalid Care Allowance) (Amendment) Regulations 2017 (SI 2017/386) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8/2017-04-10 + 2018-04-09: + value: 120 + reference: + - title: The Social Security (Invalid Care Allowance) (Amendment) Regulations 2018 (SI 2018/280) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8/2018-04-09 + 2019-04-08: + value: 123 + reference: + - title: The Social Security Benefits Up-rating Regulations 2019 (SI 2019/552) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8/2019-04-08 + 2020-04-06: + value: 128 + reference: + - title: The Social Security Benefits Up-rating Regulations 2020 (SI 2020/266) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8/2020-04-06 + 2022-04-11: + value: 132 + reference: + - title: The Social Security Benefits Up-rating Regulations 2022 (SI 2022/342) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8/2022-04-11 + 2023-04-10: + value: 139 + reference: + - title: The Social Security Benefits Up-rating Regulations 2023 (SI 2023/340) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8/2023-04-10 + 2024-04-08: + value: 151 + reference: + - title: The Social Security Benefits Up-Rating Regulations 2024 (SI 2024/386) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8/2024-04-08 + 2025-04-07: + value: 196 + reference: + - title: The Social Security Benefits Up-rating Regulations 2025 (SI 2025/352) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8/2025-04-07 + 2026-04-06: + value: 204 + reference: + - title: The Social Security Benefits Up-rating Regulations 2026 (SI 2026/218) + href: https://www.legislation.gov.uk/uksi/1976/409/regulation/8 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 index ef56a091ad..39860f1648 100644 --- 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 @@ -506,14 +506,18 @@ # overlapped to nil by maternity allowance: entitled but not paid, so still # excluded. - name: 2022, working partner entitled to Carer's Allowance overlapped to nil - the partner on contributory ESA does not meet the condition + # Carer's Allowance entitlement needs net earnings within the earnings + # limit (£132 a week in 2022-23): pay of £8,400 less half of £3,100 of + # pension contributions is £6,850, or £131.73 a week. period: 2022 input: people: parent1: age: 40 is_parent: true - employment_income: 20_000 - adjusted_net_income: 20_000 + employment_income: 8_400 + employee_pension_contributions: 3_100 + adjusted_net_income: 5_300 care_hours: 35 maternity_allowance_reported: 5_000 minimum_wage: 10 diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_earnings_limit.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_earnings_limit.yaml new file mode 100644 index 0000000000..f7ad5724ac --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/carers_allowance_earnings_limit.yaml @@ -0,0 +1,257 @@ +# Carer's Allowance earnings limit. SSCBA 1992 s. 70(1)(b) bars anyone +# gainfully employed; reg. 8(1) of the Social Security (Invalid Care +# Allowance) Regulations 1976 treats a person as gainfully employed when net +# earnings exceed £204 a week from 6 April 2026 (SI 2026/218). Net earnings +# are gross pay less income tax, primary Class 1 contributions and half of +# pension contributions (Computation of Earnings Regulations 1996 reg. +# 10(4)), and trading profit less a notional basic-rate tax and main-rate +# Class 4 (regs. 13(4) and 14). A fraction of a penny is disregarded (reg. 5). +# 2026-27: personal allowance and NI primary threshold £12,570, employee +# Class 1 main rate 8%, Class 4 lower profits limit £12,570 at 6%, basic rate +# 20%. Carer's Allowance £86.45 a week. + +- name: Case 1, pay of £8,000 (£153.85 a week, no tax or NI) is under the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 8_000 + care_hours: 40 + country: ENGLAND + output: + carers_allowance_earnings: 8_000 + is_gainfully_employed_for_carers_allowance: false + carers_allowance: 86.45 * 52 + +- name: Case 2, pay of £15,000 is £275.38 a week net, over the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 15_000 + care_hours: 40 + country: ENGLAND + output: + # Tax 20% x 2,430 = 486; NI 8% x 2,430 = 194.40. + carers_allowance_earnings: 15_000 - 486 - 194.40 + is_gainfully_employed_for_carers_allowance: true + carers_allowance_pre_overlap: 0 + carers_allowance: 0 + +- name: Case 3, earnings of exactly £204 a week have not exceeded the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 204 * 52 + care_hours: 40 + country: ENGLAND + output: + is_gainfully_employed_for_carers_allowance: false + carers_allowance: 86.45 * 52 + +- name: Case 4, one pound a year more is over the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 204 * 52 + 1 + care_hours: 40 + country: ENGLAND + output: + is_gainfully_employed_for_carers_allowance: true + carers_allowance: 0 + +- name: Case 5, half of pension contributions comes off earnings. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 11_000 + employee_pension_contributions: 1_000 + care_hours: 40 + country: ENGLAND + output: + # 11,000 - 500 = 10,500, or £201.92 a week; without the deduction it + # would be £211.54 and over the limit. + carers_allowance_earnings: 10_500 + is_gainfully_employed_for_carers_allowance: false + carers_allowance: 86.45 * 52 + +- name: Case 6, trading profit of £14,000 is £262.08 a week net, over the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + self_employment_income: 14_000 + care_hours: 40 + country: ENGLAND + output: + # Tax 20% x 1,430 = 286; Class 4 6% x 1,430 = 85.80. + carers_allowance_earnings: 14_000 - 286 - 85.80 + is_gainfully_employed_for_carers_allowance: true + carers_allowance: 0 + +- name: Case 7, a trading loss counts as nil earnings. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + self_employment_income: -5_000 + care_hours: 40 + country: ENGLAND + output: + carers_allowance_earnings: 0 + carers_allowance: 86.45 * 52 + +- name: Case 8, a reported recipient over the limit is not entitled either, but stays a carer for Universal Credit. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 15_000 + carers_allowance_reported: 4_000 + country: ENGLAND + output: + is_gainfully_employed_for_carers_allowance: true + carers_allowance: 0 + # UC regulation 30 does not apply the Carer's Allowance earnings limit. + is_carer_for_benefits: true + +- name: Case 9, savings and pension income are not earnings. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + savings_interest_income: 20_000 + private_pension_income: 20_000 + care_hours: 40 + country: ENGLAND + output: + carers_allowance_earnings: 0 + is_gainfully_employed_for_carers_allowance: false + carers_allowance: 86.45 * 52 + +- name: Case 10, the 2025-26 limit was £196. + period: 2025 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 200 * 52 + care_hours: 40 + country: ENGLAND + output: + is_gainfully_employed_for_carers_allowance: true + carers_allowance: 0 + +- name: Case 11, trading profit takes notional basic-rate tax and main-rate Class 4 only. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + self_employment_income: 60_000 + care_hours: 40 + country: ENGLAND + output: + # Reg. 14: 20% x (60,000 - 12,570) = 9,486, not the progressive tax; + # 6% x (50,270 - 12,570) = 2,262, with no additional-rate Class 4. + carers_allowance_earnings: 60_000 - 9_486 - 2_262 + is_gainfully_employed_for_carers_allowance: true + carers_allowance: 0 + +- name: Case 12, half of personal pension contributions brings trading earnings under the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + self_employment_income: 15_000 + capital_allowances: 5_000 + personal_pension_contributions: 7_700 + care_hours: 40 + country: ENGLAND + output: + # 15,000 - 20% x 2,430 (486) - 6% x 2,430 (145.80) - 7,700 / 2 (3,850) + # = 10,518.20, or £202.27 a week. Capital allowances are not deducted + # (reg. 13(6)(a)), though they cut the actual Class 4 to nil. + carers_allowance_earnings: 10_518.20 + is_gainfully_employed_for_carers_allowance: false + carers_allowance: 86.45 * 52 + +- name: Case 13, a fraction of a penny over £204 is disregarded. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 10_608.26 + care_hours: 40 + country: ENGLAND + output: + # 10,608.26 / 52 = £204.005, which counts as £204.00 (reg. 5). + is_gainfully_employed_for_carers_allowance: false + carers_allowance: 86.45 * 52 + +- name: Case 14, a whole penny over £204 is over the limit. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 10_608.52 + care_hours: 40 + country: ENGLAND + output: + # 10,608.52 / 52 = £204.01. + is_gainfully_employed_for_carers_allowance: true + carers_allowance: 0 + +- name: Case 15, pay and trading profit each take the full personal allowance. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 10_000 + self_employment_income: 5_000 + care_hours: 40 + country: ENGLAND + output: + # No tax or Class 1 on £10,000 of pay; no notional tax or Class 4 on + # £5,000 of profit under the full £12,570 (reg. 14(1)). £15,000 is + # £288.46 a week. + carers_allowance_earnings: 15_000 + is_gainfully_employed_for_carers_allowance: true + carers_allowance: 0 + +- name: Case 16, the pay's use of the allowance does not shrink the profit's. + period: 2026 + absolute_error_margin: 0.01 + input: + age: 45 + employment_income: 6_000 + self_employment_income: 8_000 + personal_pension_contributions: 6_600 + care_hours: 40 + country: ENGLAND + output: + # 6,000 + 8,000 - 6,600 / 2 = 10,700, or £205.76 a week: over the limit. + # Sharing one allowance would deduct 20% x (8,000 - 6,570) = 286 and + # give £200.27, under it. + carers_allowance_earnings: 10_700 + is_gainfully_employed_for_carers_allowance: true + carers_allowance: 0 + +- name: Case 17, before April 2024 trading earnings are also net of Class 2. + period: 2018 + absolute_error_margin: 0.01 + input: + age: 45 + self_employment_income: 7_000 + personal_pension_contributions: 1_400 + care_hours: 40 + country: ENGLAND + output: + # 2018-19: no tax or Class 4 under the £11,850 allowance and lower + # profits limit; Class 2 at £2.95 for 52 weeks (reg. 14(2)(a)) = 153.40; + # half the pension 700. 7,000 - 153.40 - 700 = 6,146.60, £118.20 a week, + # under the £120 limit. Without Class 2 it would be £121.15, over it. + carers_allowance_earnings: 6_146.60 + is_gainfully_employed_for_carers_allowance: false + carers_allowance: 64.60 * 52 diff --git a/policyengine_uk/tests/test_carers_allowance_earnings_limit_properties.py b/policyengine_uk/tests/test_carers_allowance_earnings_limit_properties.py new file mode 100644 index 0000000000..497d9ad7f9 --- /dev/null +++ b/policyengine_uk/tests/test_carers_allowance_earnings_limit_properties.py @@ -0,0 +1,170 @@ +"""Carer's Allowance earnings limit. + +SSCBA 1992 s. 70(1)(b) bars a person who is gainfully employed, and reg. 8(1) +of the Social Security (Invalid Care Allowance) Regulations 1976 treats a +person as gainfully employed when their earnings exceed the limit (£204 a +week from 6 April 2026). Earnings are net of income tax, primary Class 1 (or, +for trading profit, Class 4) contributions and half of pension contributions +(Computation of Earnings Regulations 1996 regs. 10(4), 13 and 14; reg. 14(2)(a), +which deducted Class 2, was omitted from 6 April 2024). + +Invariants, for a carer in England in 2026 caring 35 hours or more, with pay, +trading profit, employee pension contributions and unearned income drawn +freely: + +1. Bounds: 0 <= carers_allowance_earnings <= pay + max(0, profit). +2. Differential: the test is passed exactly when + carers_allowance_earnings / 52 <= 204, and the allowance is the full rate + then and nil otherwise. +3. Monotonic: raising pay never lowers net earnings, so a person over the + limit stays over it. +4. Unearned income is not earnings: savings interest and pension income + leave carers_allowance_earnings unchanged. +""" + +import numpy as np +import pytest +from hypothesis import HealthCheck, given, settings +from hypothesis import strategies as st + +from policyengine_uk import Simulation +from policyengine_uk.utils.class_2 import class_2_contribution_weeks + +LIMIT = 204 +RATE = 86.45 +WEEKS = 52 + + +def _person(pay, profit, pension, savings=0.0, private_pension=0.0): + situation = { + "people": { + "carer": { + "age": {"2026": 45}, + "employment_income": {"2026": pay}, + "self_employment_income": {"2026": profit}, + "employee_pension_contributions": {"2026": pension}, + "savings_interest_income": {"2026": savings}, + "private_pension_income": {"2026": private_pension}, + "care_hours": {"2026": 40}, + } + }, + "benunits": {"benunit": {"members": ["carer"]}}, + "households": { + "household": {"members": ["carer"], "country": {"2026": "ENGLAND"}} + }, + } + sim = Simulation(situation=situation) + return { + name: float(np.asarray(sim.calculate(name, 2026))[0]) + for name in ( + "carers_allowance_earnings", + "is_gainfully_employed_for_carers_allowance", + "carers_allowance", + ) + } + + +money = st.floats(min_value=0, max_value=60_000, allow_nan=False) +profit_money = st.floats(min_value=-20_000, max_value=60_000, allow_nan=False) + + +@settings( + max_examples=12, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) +@given(pay=money, profit=profit_money, pension_share=st.floats(0, 0.3)) +def test_bounds_and_differential(pay, profit, pension_share): + pension = pay * pension_share + result = _person(pay, profit, pension) + earnings = result["carers_allowance_earnings"] + assert -0.01 <= earnings <= pay + max(0, profit) + 0.01 + # Regulation 5: a fraction of a penny is disregarded. + over = np.floor(earnings / WEEKS * 100 + 1e-6) / 100 > LIMIT + assert bool(result["is_gainfully_employed_for_carers_allowance"]) == over + expected = 0 if over else RATE * WEEKS + assert abs(result["carers_allowance"] - expected) < 0.01 + + +@settings( + max_examples=8, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) +@given(pay=money, rise=st.floats(min_value=1, max_value=20_000)) +def test_net_earnings_never_fall_as_pay_rises(pay, rise): + lower = _person(pay, 0, 0) + higher = _person(pay + rise, 0, 0) + assert ( + higher["carers_allowance_earnings"] >= lower["carers_allowance_earnings"] - 0.01 + ) + if lower["is_gainfully_employed_for_carers_allowance"]: + assert higher["is_gainfully_employed_for_carers_allowance"] + + +@settings( + max_examples=6, + deadline=None, + suppress_health_check=[HealthCheck.too_slow], +) +@given( + pay=st.floats(min_value=0, max_value=15_000), + savings=money, + private_pension=money, +) +def test_unearned_income_is_not_earnings(pay, savings, private_pension): + alone = _person(pay, 0, 0) + with_unearned = _person(pay, 0, 0, savings=savings, private_pension=private_pension) + assert ( + abs( + alone["carers_allowance_earnings"] + - with_unearned["carers_allowance_earnings"] + ) + < 0.01 + ) + + +@pytest.mark.parametrize("year", [2016, 2017, 2018, 2026]) +@pytest.mark.parametrize("country", ["ENGLAND", "SCOTLAND"]) +def test_trading_profit_takes_the_basic_rate_in_every_year(year, country): + """Reg. 14(1): the basic rate, or for a Scottish taxpayer the Scottish + basic rate, which were both 20% in each of these years. Before 2018-19 the + Scottish scale has no starter rate, so reading its second bracket by + position at an instant would pick the higher rate. Before 2024-25 reg. + 14(2)(a) also deducted Class 2: £30,000 is above the small profits + threshold in each year, so a full year of the flat rate.""" + profit = 30_000 + simulation = Simulation( + situation={ + "people": { + "carer": { + "age": {year: 45}, + "self_employment_income": {year: profit}, + "care_hours": {year: 40}, + } + }, + "benunits": {"family": {"members": ["carer"]}}, + "households": {"home": {"members": ["carer"], "country": {year: country}}}, + } + ) + hmrc = simulation.tax_benefit_system.parameters(year).gov.hmrc + allowance = hmrc.income_tax.allowances.personal_allowance.amount + class_4 = hmrc.national_insurance.class_4 + class_2 = hmrc.national_insurance.class_2 + class_2_weeks = class_2_contribution_weeks(year) + expected = ( + profit + - 0.20 * (profit - allowance) + - class_2.flat_rate * class_2_weeks + - class_4.rates.main + * ( + min(profit, class_4.thresholds.upper_profits_limit) + - class_4.thresholds.lower_profits_limit + ) + ) + assert bool(simulation.calculate("pays_scottish_income_tax", year)[0]) == ( + country == "SCOTLAND" + ) + assert simulation.calculate("carers_allowance_earnings", year)[0] == pytest.approx( + expected, abs=0.01 + ) diff --git a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py index 8795cdd8c6..e317bf68e5 100644 --- a/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py +++ b/policyengine_uk/tests/test_council_tax_reduction_carer_income_properties.py @@ -192,6 +192,12 @@ def situation(families): "private_pension_income": {YEAR: float(pension) * (j == 0)}, } if j == fam["carer"]: + # Keep the carer's pay within the Carer's Allowance + # earnings limit (£204 a week in 2026-27), so the + # carer stays entitled; £10,000 is £192 a week. + person["employment_income"] = { + YEAR: min(a.get("employment_income", 0.0), 10_000.0) + } if fam["by_hours"]: person["care_hours"] = {YEAR: 35} else: diff --git a/policyengine_uk/variables/gov/dwp/carers_allowance_earnings.py b/policyengine_uk/variables/gov/dwp/carers_allowance_earnings.py new file mode 100644 index 0000000000..d9d7e6f167 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/carers_allowance_earnings.py @@ -0,0 +1,117 @@ +from policyengine_uk.model_api import * +from policyengine_uk.utils.class_2 import class_2_contribution_weeks, class_2_liable + + +class carers_allowance_earnings(Variable): + value_type = float + entity = Person + label = "earnings for the Carer's Allowance earnings limit" + documentation = ( + "Net earnings from employment and self-employment, as the Carer's " + "Allowance gainful-employment test counts them (Computation of " + "Earnings Regulations 1996), less half of the person's pension " + "contributions. A trading loss counts as nil.\n\n" + "Employment (reg. 10(4)): gross pay less the income tax and primary " + "Class 1 contributions deducted from it. The law deducts the tax " + "actually withheld; the model approximates it as the tax PAYE would " + "take on a standard code if the pay were the person's only income " + "(pay less net-pay pension contributions and the personal allowance, " + "at rest-of-UK or Scottish rates). That also avoids a circularity, " + "since Carer's Allowance is itself taxable, and makes unearned income " + "irrelevant here by assumption.\n\n" + "Self-employment (regs. 13(4) and 14): trading profit less a notional " + "income tax at the basic rate (the Scottish basic rate for a Scottish " + "taxpayer) on the profit above the full personal allowance, and Class 4 " + "contributions at the main rate on the profit between the lower and " + "upper profits limits. Regulation 14(1) deducts the personal reliefs " + "the claimant is entitled to, with no reduction for any allowance the " + "pay uses, and capital expenditure is not deducted (reg. 13(6)(a)). " + "Until 6 April 2024 reg. 14(2)(a) also deducted the Class 2 " + "contributions payable on the chargeable profit; that paragraph was " + "omitted with compulsory Class 2, whose flat rate is nil from " + "2024-25.\n\n" + "Not modelled: the deduction for care charges (Schedule 3, up to half " + "of net earnings) and the Schedule 1 disregards, so this can overstate " + "earnings for a carer who pays for care while at work." + ) + definition_period = YEAR + unit = GBP + reference = [ + dict( + title="Social Security Benefit (Computation of Earnings) Regulations 1996 reg. 10(4)", + href="https://www.legislation.gov.uk/uksi/1996/2745/regulation/10", + ), + dict( + title="Social Security Benefit (Computation of Earnings) Regulations 1996 reg. 13", + href="https://www.legislation.gov.uk/uksi/1996/2745/regulation/13", + ), + dict( + title="Social Security Benefit (Computation of Earnings) Regulations 1996 reg. 14", + href="https://www.legislation.gov.uk/uksi/1996/2745/regulation/14", + ), + ] + + def formula(person, period, parameters): + p = parameters(period).gov.hmrc + income_tax = p.income_tax + class_4 = p.national_insurance.class_4 + scottish = person("pays_scottish_income_tax", period) + personal_allowance = income_tax.allowances.personal_allowance.amount + + # Employment: PAYE on the pay alone (reg. 10(4)(a)). + pay = person("employment_income", period) + taxable_pay = max_(0, pay - person("employee_pension_contributions", period)) + pay_tax = where( + scottish, + income_tax.rates.scotland.rates.calc( + max_(0, taxable_pay - personal_allowance) + ), + income_tax.rates.uk.calc(max_(0, taxable_pay - personal_allowance)), + ) + primary_class_1 = person("ni_class_1_employee", period) + + # Self-employment: notional basic-rate tax and main-rate Class 4 + # (reg. 14(1)-(2)). The tax is on the profit as if it were the only + # income, less the full personal allowance. + profit = max_(0, person("self_employment_income", period)) + # The basic rate is the first band of the rest-of-UK scale (ITA 2007 + # s. 6(2)). The Scottish basic rate is the second bracket of the + # Scottish scale, after the starter rate; read it from the parameter + # tree, which keeps the null starter bracket of years before 2018-19 in + # place. The scale at an instant drops null brackets, so its index 1 + # was the higher rate in 2016-17 and 2017-18. + scottish_scale = person.simulation.tax_benefit_system.parameters.gov.hmrc.income_tax.rates.scotland.rates + basic_rate = where( + scottish, + scottish_scale.brackets[1].rate(period.start), + income_tax.rates.uk.rates[0], + ) + trading_tax = basic_rate * max_(0, profit - personal_allowance) + trading_class_4 = class_4.rates.main * max_( + 0, + min_(profit, class_4.thresholds.upper_profits_limit) + - class_4.thresholds.lower_profits_limit, + ) + # Reg. 14(2)(a), omitted from 6 April 2024: the Class 2 contributions + # payable on the chargeable profit (SSCBA 1992 s. 11), on the + # profit itself rather than ni_class_2's base after capital + # allowances. The flat rate is nil from 2024-25. + class_2 = p.national_insurance.class_2 + trading_class_2 = ( + class_2_liable(profit, class_2) + * class_2.flat_rate + * class_2_contribution_weeks(period.start.year) + ) + + half_pension_contributions = 0.5 * person("pension_contributions", period) + return max_( + 0, + pay + - pay_tax + - primary_class_1 + + profit + - trading_tax + - trading_class_4 + - trading_class_2 + - half_pension_contributions, + ) diff --git a/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py b/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py index ca06fafbb2..6bca6f6b93 100644 --- a/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py +++ b/policyengine_uk/variables/gov/dwp/carers_allowance_pre_overlap.py @@ -25,9 +25,16 @@ def formula(person, period, parameters): weekly_care_hours = person("care_hours", period) meets_work_condition = weekly_care_hours >= ca.min_hours would_claim = person("would_claim_carers_allowance", period) + # SSCBA 1992 s. 70(1)(b): no allowance for anyone gainfully employed, + # which reg. 8 of the 1976 Regulations defines by an earnings limit. + # It applies to reported recipients too. + gainfully_employed = person( + "is_gainfully_employed_for_carers_allowance", period + ) eligible = ( ~(in_scotland & csp_replaces_ca) & (meets_work_condition | receives_ca) + & ~gainfully_employed & would_claim ) return eligible * ca.rate * WEEKS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/is_gainfully_employed_for_carers_allowance.py b/policyengine_uk/variables/gov/dwp/is_gainfully_employed_for_carers_allowance.py new file mode 100644 index 0000000000..b6e6b4b981 --- /dev/null +++ b/policyengine_uk/variables/gov/dwp/is_gainfully_employed_for_carers_allowance.py @@ -0,0 +1,39 @@ +from policyengine_uk.model_api import * + + +class is_gainfully_employed_for_carers_allowance(Variable): + value_type = bool + entity = Person + label = "gainfully employed for Carer's Allowance" + documentation = ( + "Whether the person's weekly net earnings exceed the Carer's Allowance " + "earnings limit, so that they are treated as gainfully employed and " + "are not entitled to the allowance. The regulation tests the earnings " + "of the preceding week; annual earnings are spread evenly over 52 " + "weeks. A fraction of a penny is disregarded, as regulation 5 of the " + "Computation of Earnings Regulations requires when that favours the " + "claimant." + ) + definition_period = YEAR + reference = [ + dict( + title="Social Security Contributions and Benefits Act 1992 s. 70(1)(b)", + href="https://www.legislation.gov.uk/ukpga/1992/4/section/70", + ), + dict( + title="The Social Security (Invalid Care Allowance) Regulations 1976 reg. 8(1)", + href="https://www.legislation.gov.uk/uksi/1976/409/regulation/8", + ), + dict( + title="Social Security Benefit (Computation of Earnings) Regulations 1996 reg. 5", + href="https://www.legislation.gov.uk/uksi/1996/2745/regulation/5", + ), + ] + + def formula(person, period, parameters): + limit = parameters(period).gov.dwp.carers_allowance.earnings_limit + weekly = person("carers_allowance_earnings", period) / WEEKS_IN_YEAR + # Disregard a fraction of a penny; the tolerance keeps an exact + # number of pence from being knocked down by floating-point error. + weekly_in_pence = np.floor(weekly * 100 + 1e-6) / 100 + return weekly_in_pence > limit diff --git a/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py b/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py index 4c5e80aa56..f86e87520d 100644 --- a/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py +++ b/policyengine_uk/variables/household/demographic/is_carer_for_benefits.py @@ -20,15 +20,31 @@ class is_carer_for_benefits(Variable): "person cared for receives a qualifying disability benefit (part of " "the Carer's Allowance conditions referenced by regulation 30), so a " "survey hours field counts caring for " - "anyone and a dataset's take-up input carries that simplification." + "anyone and a dataset's take-up input carries that simplification. " + "A reported Carer's Allowance award the person would claim also " + "counts, so a carer whose earnings put them over the Carer's Allowance " + "earnings limit keeps the Universal Credit carer element, which does " + "not apply that limit (regulation 30(1)(a)(i))." ) reference = "https://www.legislation.gov.uk/uksi/2013/376/regulation/30" def formula(person, period, parameters): min_hours = parameters(period).gov.dwp.carers_allowance.min_hours meets_hours = person("care_hours", period) >= min_hours + # A reported Carer's Allowance award the person would claim is + # evidence of the caring itself, where no hours are recorded. It still + # counts when the earnings limit (which the Universal Credit carer + # element does not apply) stops the allowance being paid. Requiring + # would_claim is a modelling convention, not the law: regulation 30(2) + # applies whether or not a claim is made. It keeps an award the person + # would not claim counting no more than it did before; in the data, + # would_claim follows reported receipt. + reports_carers_allowance = ( + person("carers_allowance_reported", period) > 0 + ) & person("would_claim_carers_allowance", period) return ( person("receives_carer_benefit", period) | person("is_entitled_to_carer_benefit", period) | meets_hours + | reports_carers_allowance )