diff --git a/changelog.d/1818.fixed.md b/changelog.d/1818.fixed.md new file mode 100644 index 000000000..4bda6ec7a --- /dev/null +++ b/changelog.d/1818.fixed.md @@ -0,0 +1,4 @@ +- Benefit cap earnings exception (Universal Credit Regulations 2013 reg. 82) now deducts only the tax attributable to earnings, rather than the claimant's whole income tax liability, so property, savings, dividend and pension income no longer erode net earnings and wrongly remove the exception. Relievable pension contributions are now deducted as reg. 55(5) requires. +- Moved the benefit cap earnings threshold out of three hardcoded copies of `10_152` into the dated parameter `gov.dwp.benefit_cap_earnings_exemption`, with values from 2020-21 to 2026-27 (2026-27 rises to £881 a month) derived from 16 hours a week at the National Living Wage. +- Armed Forces Independence Payment now excepts a benefit unit from the benefit cap, as GOV.UK and the regulations provide. The variable has no dataset source, so this changes household calculations only. +- Removed dead copy-pasted code from the three `is_benefit_cap_exempt_*` variables, which computed exemption conditions that were never returned. diff --git a/policyengine_uk/parameters/gov/dwp/benefit_cap_earnings_exemption.yaml b/policyengine_uk/parameters/gov/dwp/benefit_cap_earnings_exemption.yaml new file mode 100644 index 000000000..ac9d86ca4 --- /dev/null +++ b/policyengine_uk/parameters/gov/dwp/benefit_cap_earnings_exemption.yaml @@ -0,0 +1,22 @@ +description: Monthly earned income at or above which a Universal Credit claimant (or couple, jointly) is excepted from the benefit cap. Set by regulation 82 of the Universal Credit Regulations 2013 as 16 hours per week at the National Living Wage rate in regulation 4 of the National Minimum Wage Regulations 2015, converted to a monthly amount by multiplying by 52 and dividing by 12, and rounded down to whole pounds by DWP. Earned income for this test is net of income tax, National Insurance contributions and relievable pension contributions on the earnings, and excludes income treated as earned under the minimum income floor. +values: + 2020-04-01: 604 + 2021-04-01: 617 + 2022-04-01: 658 + 2023-04-01: 722 + 2024-04-01: 793 + 2025-04-01: 846 + 2026-04-01: 881 +metadata: + label: Benefit cap earnings exemption threshold + unit: currency-GBP + period: month + reference: + - title: The Universal Credit Regulations 2013 reg. 82 (Exceptions - earnings) + href: https://www.legislation.gov.uk/uksi/2013/376/regulation/82 + - title: The Universal Credit Regulations 2013 reg. 55 (Employed earnings) + href: https://www.legislation.gov.uk/uksi/2013/376/regulation/55 + - title: GOV.UK - Benefit cap - When you're not affected + href: https://www.gov.uk/benefit-cap/when-youre-not-affected + - title: GOV.UK - National Minimum Wage and National Living Wage rates + href: https://www.gov.uk/national-minimum-wage-rates diff --git a/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/is_benefit_cap_exempt.yaml b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/is_benefit_cap_exempt.yaml new file mode 100644 index 000000000..10f03798d --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/dwp/benefit_cap/is_benefit_cap_exempt.yaml @@ -0,0 +1,193 @@ +# Benefit cap exceptions, Universal Credit Regulations 2013 regs. 82-83. +# https://www.legislation.gov.uk/uksi/2013/376/regulation/82 +# https://www.gov.uk/benefit-cap/when-youre-not-affected + +# --- Earnings exception: only tax on the earnings is deducted (reg. 55(5)) --- + +- name: Unearned income does not reduce net earnings for the earnings exception + # Regulation 55(5) deducts the income tax paid "in respect of the + # employment", not the claimant's whole income tax liability. Property + # income of 40,000 pushes total income tax to over 8,000, but the tax + # attributable to the 13,000 of earnings is under 100, so net earnings stay + # well above the 846/month threshold and the exception applies. + period: 2025 + input: + people: + person: + age: 35 + employment_income: 13_000 + property_income: 40_000 + benunits: + benunit: + members: [person] + output: + is_benefit_cap_exempt_earnings: true + is_benefit_cap_exempt: true + +- name: Earnings below the threshold do not except, even with large unearned income + # The same unearned income with earnings of 8,000 leaves net earnings below + # 846 x 12 = 10,152, so the exception does not apply. + period: 2025 + input: + people: + person: + age: 35 + employment_income: 8_000 + property_income: 40_000 + benunits: + benunit: + members: [person] + output: + is_benefit_cap_exempt_earnings: false + +- name: A couple's earnings are combined for the earnings exception + # Reg. 82(1)(a) applies to "the claimant's and their partner's combined + # earned income". Neither partner alone reaches 10,152; together they do. + period: 2025 + input: + people: + person1: + age: 35 + employment_income: 6_000 + person2: + age: 35 + employment_income: 6_000 + benunits: + benunit: + members: [person1, person2] + output: + is_benefit_cap_exempt_earnings: true + +- name: Relievable pension contributions reduce earned income + # Reg. 55(5)(a) deducts relievable pension contributions from employed + # earnings, so a contribution that takes net earnings below the threshold + # removes the exception. + period: 2025 + input: + people: + person: + age: 35 + employment_income: 11_000 + employee_pension_contributions: 1_500 + benunits: + benunit: + members: [person] + output: + is_benefit_cap_exempt_earnings: false + +# --- Earnings threshold is a dated parameter, not a hardcoded constant --- + +- name: The 2025-26 earnings threshold is 846 a month + period: 2025 + input: + people: + person: + age: 35 + employment_income: 10_300 + benunits: + benunit: + members: [person] + output: + # 10,300 clears 846 x 12 = 10,152. + is_benefit_cap_exempt_earnings: true + +- name: The 2026-27 earnings threshold rises to 881 a month + period: 2026 + input: + people: + person: + age: 35 + employment_income: 10_300 + benunits: + benunit: + members: [person] + output: + # The same earnings fall short of 881 x 12 = 10,572. + is_benefit_cap_exempt_earnings: false + +- name: Earnings above the 2026-27 threshold except from the cap + period: 2026 + input: + people: + person: + age: 35 + employment_income: 10_600 + benunits: + benunit: + members: [person] + output: + is_benefit_cap_exempt_earnings: true + +# --- Armed Forces Independence Payment is a statutory exception --- + +- name: Armed Forces Independence Payment excepts from the benefit cap + period: 2026 + input: + people: + person: + age: 35 + armed_forces_independence_payment: 5_740 + benunits: + benunit: + members: [person] + output: + is_benefit_cap_exempt_health_disability: true + is_benefit_cap_exempt: true + +- name: Without Armed Forces Independence Payment the cap applies + period: 2026 + input: + people: + person: + age: 35 + armed_forces_independence_payment: 0 + benunits: + benunit: + members: [person] + output: + is_benefit_cap_exempt_health_disability: false + is_benefit_cap_exempt: false + +- name: A child's Armed Forces Independence Payment excepts the benefit unit + period: 2026 + input: + people: + parent: + age: 35 + child: + age: 10 + armed_forces_independence_payment: 5_740 + benunits: + benunit: + members: [parent, child] + output: + is_benefit_cap_exempt_health_disability: true + +# --- Other exceptions are unchanged --- + +- name: State pension age excepts from the benefit cap + period: 2026 + input: + people: + person: + age: 70 + benunits: + benunit: + members: [person] + output: + is_benefit_cap_exempt_other: true + is_benefit_cap_exempt: true + +- name: Armed Forces Compensation Scheme payments except from the benefit cap + period: 2026 + input: + people: + person: + age: 35 + afcs_reported: 2_000 + benunits: + benunit: + members: [person] + output: + is_benefit_cap_exempt_other: true + is_benefit_cap_exempt_health_disability: true diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt.py index 563b34085..6bd501899 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt.py @@ -4,7 +4,7 @@ class is_benefit_cap_exempt(Variable): value_type = bool entity = BenUnit - label = "Whether exempt from the benefits cap because of health or disability" + label = "Whether exempt from the benefits cap" definition_period = YEAR reference = "https://www.gov.uk/benefit-cap/when-youre-not-affected" diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py index c27e13c39..f0f8d1f3c 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_earnings.py @@ -4,57 +4,38 @@ class is_benefit_cap_exempt_earnings(Variable): value_type = bool entity = BenUnit - label = "Whether exempt from the benefits cap for non-health/disability reasons" + label = "Whether exempt from the benefits cap because of earnings" definition_period = YEAR - reference = "https://www.gov.uk/benefit-cap/when-youre-not-affected" + reference = "https://www.legislation.gov.uk/uksi/2013/376/regulation/82" def formula(benunit, period, parameters): - # Check if anyone in benefit unit is over state pension age - person = benunit.members - over_pension_age = person("is_SP_age", period) - has_pensioner = benunit.any(over_pension_age) - - # UC-specific exemptions - # Limited capability for work and work-related activity - has_lcwra = benunit.any(person("uc_limited_capability_for_WRA", period)) - - # Carer element in UC indicates caring for someone with disability - gets_uc_carer_element = benunit("uc_carer_element", period) > 0 - - # Earnings exemption for UC (£846/month = £10,152/year) - # Note: Only check earned income, not UC amount itself to avoid circular dependency - uc_earned = benunit.sum( - benunit.members("employment_income", period) - + benunit.members("self_employment_income", period) - - benunit.members("income_tax", period) - - benunit.members("national_insurance", period) + # Regulation 82 of the Universal Credit Regulations 2013 excepts a + # claimant (or couple, on their combined income) from the benefit cap + # where their earned income reaches 16 hours a week at the National + # Living Wage. Earned income here is employed and self-employed + # earnings net of the income tax, National Insurance and relievable + # pension contributions attributable to those earnings (reg. 55(5), + # reg. 57), not net of total tax on all income. + # + # Only earned income is read, not the Universal Credit award itself, + # to avoid a circular dependency with the cap. + earnings = add( + benunit, + period, + ["employment_income", "self_employment_income"], ) - earnings_threshold = 10_152 - meets_earnings_test = uc_earned >= earnings_threshold - - # Disability and carer benefits that exempt from cap - QUAL_PERSONAL_BENEFITS = [ - "attendance_allowance", - "carers_allowance", - "dla", # Disability Living Allowance (includes components) - "pip_dl", # PIP daily living component - "pip_m", # PIP mobility component - "iidb", # Industrial injuries disability benefit - ] - - # ESA and Working Tax Credit - QUAL_BENUNIT_BENEFITS = [ - "esa_income", # Income-based ESA - "working_tax_credit", # If getting WTC, likely working enough - ] - - qualifying_personal_benefits = add(benunit, period, QUAL_PERSONAL_BENEFITS) - qualifying_benunit_benefits = add(benunit, period, QUAL_BENUNIT_BENEFITS) - - # Check for Armed Forces Compensation Scheme payments - afcs = benunit("afcs", period) > 0 - - # ESA contribution-based with support component - esa_support_component = benunit("esa_contrib", period) > 0 + # earned_income_tax is the tax on non-savings, non-dividend income, + # which excludes property, savings and dividend income. It is the + # closest available measure of tax on earnings; it still includes tax + # on private and state pension income, which is the approximation we + # accept here (households with pension income are generally already + # exempt through the state pension age exception). + deductions = add( + benunit, + period, + ["earned_income_tax", "national_insurance", "pension_contributions"], + ) + net_earnings = max_(0, earnings - deductions) - return meets_earnings_test + monthly_threshold = parameters(period).gov.dwp.benefit_cap_earnings_exemption + return net_earnings >= monthly_threshold * MONTHS_IN_YEAR diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py index 69d9205c9..a738b71b5 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_health_disability.py @@ -9,29 +9,14 @@ class is_benefit_cap_exempt_health_disability(Variable): reference = "https://www.gov.uk/benefit-cap/when-youre-not-affected" def formula(benunit, period, parameters): - # Check if anyone in benefit unit is over state pension age person = benunit.members - over_pension_age = person("is_SP_age", period) - has_pensioner = benunit.any(over_pension_age) - # UC-specific exemptions # Limited capability for work and work-related activity has_lcwra = benunit.any(person("uc_limited_capability_for_WRA", period)) # Carer element in UC indicates caring for someone with disability gets_uc_carer_element = benunit("uc_carer_element", period) > 0 - # Earnings exemption for UC (£846/month = £10,152/year) - # Note: Only check earned income, not UC amount itself to avoid circular dependency - uc_earned = benunit.sum( - benunit.members("employment_income", period) - + benunit.members("self_employment_income", period) - - benunit.members("income_tax", period) - - benunit.members("national_insurance", period) - ) - earnings_threshold = 10_152 - meets_earnings_test = uc_earned >= earnings_threshold - # Disability and carer benefits that exempt from cap QUAL_PERSONAL_BENEFITS = [ "attendance_allowance", @@ -41,6 +26,8 @@ def formula(benunit, period, parameters): "pip_dl", # PIP daily living component "pip_m", # PIP mobility component "iidb", # Industrial injuries disability benefit + # Armed Forces Independence Payment is a statutory exemption + "armed_forces_independence_payment", ] # ESA and Working Tax Credit diff --git a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py index 0fb154750..88f66c2f8 100644 --- a/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py +++ b/policyengine_uk/variables/gov/dwp/is_benefit_cap_exempt_other.py @@ -9,49 +9,11 @@ class is_benefit_cap_exempt_other(Variable): reference = "https://www.gov.uk/benefit-cap/when-youre-not-affected" def formula(benunit, period, parameters): - # Check if anyone in benefit unit is over state pension age + # Anyone in the benefit unit over state pension age person = benunit.members - over_pension_age = person("is_SP_age", period) - has_pensioner = benunit.any(over_pension_age) + has_pensioner = benunit.any(person("is_SP_age", period)) - # UC-specific exemptions - # Limited capability for work and work-related activity - has_lcwra = benunit.any(person("uc_limited_capability_for_WRA", period)) - - # Carer element in UC indicates caring for someone with disability - gets_uc_carer_element = benunit("uc_carer_element", period) > 0 - - # Earnings exemption for UC (£846/month = £10,152/year) - # Note: Only check earned income, not UC amount itself to avoid circular dependency - uc_earned = benunit.sum( - benunit.members("employment_income", period) - + benunit.members("self_employment_income", period) - - benunit.members("income_tax", period) - - benunit.members("national_insurance", period) - ) - earnings_threshold = 10_152 - meets_earnings_test = uc_earned >= earnings_threshold - - # Disability and carer benefits that exempt from cap - QUAL_PERSONAL_BENEFITS = [ - "attendance_allowance", - "carers_allowance", - "dla", # Disability Living Allowance (includes components) - "pip_dl", # PIP daily living component - "pip_m", # PIP mobility component - "iidb", # Industrial injuries disability benefit - ] - - # ESA and Working Tax Credit - QUAL_BENUNIT_BENEFITS = [ - "esa_income", # Income-based ESA - "working_tax_credit", # If getting WTC, likely working enough - ] - - qualifying_personal_benefits = add(benunit, period, QUAL_PERSONAL_BENEFITS) - qualifying_benunit_benefits = add(benunit, period, QUAL_BENUNIT_BENEFITS) - - # Check for Armed Forces Compensation Scheme payments + # Armed Forces Compensation Scheme payments afcs = benunit("afcs", period) > 0 # ESA contribution-based with support component