diff --git a/changelog.d/2371.fixed.md b/changelog.d/2371.fixed.md new file mode 100644 index 0000000000..204233b876 --- /dev/null +++ b/changelog.d/2371.fixed.md @@ -0,0 +1 @@ +- Included statutory sick and maternity pay in the relevant earnings limits for pension contribution relief and annual-allowance income tests. diff --git a/policyengine_uk/parameters/gov/hmrc/income_tax/reliefs/pension_contribution/README.md b/policyengine_uk/parameters/gov/hmrc/income_tax/reliefs/pension_contribution/README.md index 724d510f14..ae3324a83b 100644 --- a/policyengine_uk/parameters/gov/hmrc/income_tax/reliefs/pension_contribution/README.md +++ b/policyengine_uk/parameters/gov/hmrc/income_tax/reliefs/pension_contribution/README.md @@ -1 +1,19 @@ # Pension contributions + +Relevant UK earnings for pension contribution relief include employer-paid, +taxable statutory sick pay and statutory maternity pay. The model holds these +payments in `employment_benefits`, separately from `employment_income`, and +includes that aggregate once when limiting relief. Pension income does not +increase the relevant-earnings limit. The existing age limit, relief-at-source +basic amount and nonnegative trading-profit rules still apply. + +This follows [Finance Act 2004 section 189](https://www.legislation.gov.uk/ukpga/2004/12/section/189) +and [HMRC PTM044100](https://www.gov.uk/hmrc-internal-manuals/pensions-tax-manual/ptm044100). +The annual monetary inputs represent actual employer-paid taxable payments; +they do not establish statutory-pay eligibility or identify exceptional payments +made directly by HMRC in survey data. + +Statutory paternity pay still depends on the separate change in +[PR #2368](https://github.com/PolicyEngine/policyengine-uk/pull/2368) to include it +in the taxable employment aggregate. This change addresses the existing SSP/SMP +omission in issue #2371; it does not complete that issue's SPP dependency. diff --git a/policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/statutory_pay_pension_relief.yaml b/policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/statutory_pay_pension_relief.yaml new file mode 100644 index 0000000000..ebf7430268 --- /dev/null +++ b/policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/statutory_pay_pension_relief.yaml @@ -0,0 +1,243 @@ +# Actual employer-paid annual SSP/SMP, occupational net-pay member contributions, +# and an already-awarded defined-benefit pension where specified. +# FA 2004 s. 189 and HMRC PTM044100: statutory employment pay is relevant earnings. + +- name: SSP final ordinary tax + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 60 + employment_income: 20000 + statutory_sick_pay: 2000 + statutory_maternity_pay: 0 + self_employment_income: 0 + private_pension_income: 20000 + employee_pension_contributions_reported: 22000 + personal_pension_contributions: 0 + output: + employment_benefits: 2000 + employee_pension_contributions: 22000 + pension_contributions_relief: 22000 + personal_pension_contributions_tax: 0 + income_tax: 1486.0 + +- name: SMP relief cap + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 35 + employment_income: 20000 + statutory_sick_pay: 0 + statutory_maternity_pay: 2000 + self_employment_income: 0 + private_pension_income: 0 + employee_pension_contributions_reported: 22000 + personal_pension_contributions: 0 + output: + employment_benefits: 2000 + employee_pension_contributions: 22000 + pension_contributions_relief: 22000 + personal_pension_contributions_tax: 0 + income_tax: 0.0 + +- name: Still capped at all relevant earnings + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 60 + employment_income: 20000 + statutory_sick_pay: 2000 + statutory_maternity_pay: 0 + self_employment_income: 0 + private_pension_income: 20000 + employee_pension_contributions_reported: 25000 + personal_pension_contributions: 0 + output: + employment_benefits: 2000 + employee_pension_contributions: 25000 + pension_contributions_relief: 22000 + personal_pension_contributions_tax: 0 + income_tax: 1486.0 + +- name: Contributions already covered + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 60 + employment_income: 20000 + statutory_sick_pay: 2000 + statutory_maternity_pay: 0 + self_employment_income: 0 + private_pension_income: 20000 + employee_pension_contributions_reported: 12000 + personal_pension_contributions: 0 + output: + employment_benefits: 2000 + employee_pension_contributions: 12000 + pension_contributions_relief: 12000 + personal_pension_contributions_tax: 0 + income_tax: 3486.0 + +- name: Equal amount as wages + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 60 + employment_income: 22000 + statutory_sick_pay: 0 + statutory_maternity_pay: 0 + self_employment_income: 0 + private_pension_income: 20000 + employee_pension_contributions_reported: 22000 + personal_pension_contributions: 0 + output: + employment_benefits: 0 + employee_pension_contributions: 22000 + pension_contributions_relief: 22000 + personal_pension_contributions_tax: 0 + income_tax: 1486.0 + +- name: Pension is not relevant earnings + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 60 + employment_income: 20000 + statutory_sick_pay: 0 + statutory_maternity_pay: 0 + self_employment_income: 0 + private_pension_income: 20000 + employee_pension_contributions_reported: 22000 + personal_pension_contributions: 0 + output: + employment_benefits: 0 + employee_pension_contributions: 22000 + pension_contributions_relief: 20000 + personal_pension_contributions_tax: 0 + income_tax: 1486.0 + +- name: Age75 exclusion retained + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 75 + employment_income: 20000 + statutory_sick_pay: 2000 + statutory_maternity_pay: 0 + self_employment_income: 0 + private_pension_income: 20000 + employee_pension_contributions_reported: 22000 + personal_pension_contributions: 0 + output: + employment_benefits: 2000 + employee_pension_contributions: 22000 + pension_contributions_relief: 0 + personal_pension_contributions_tax: 0 + income_tax: 5886.0 + +- name: RAS basic amount retained + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 40 + employment_income: 1000 + statutory_sick_pay: 0 + statutory_maternity_pay: 1000 + self_employment_income: 0 + private_pension_income: 0 + employee_pension_contributions_reported: 0 + personal_pension_contributions: 3600 + output: + employment_benefits: 1000 + employee_pension_contributions: 0 + pension_contributions_relief: 3600 + personal_pension_contributions_tax: 0 + income_tax: 0.0 + +- name: Positive trading profit + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 60 + employment_income: 20000 + statutory_sick_pay: 2000 + statutory_maternity_pay: 0 + self_employment_income: 5000 + private_pension_income: 20000 + employee_pension_contributions_reported: 27000 + personal_pension_contributions: 0 + output: + employment_benefits: 2000 + employee_pension_contributions: 27000 + pension_contributions_relief: 27000 + personal_pension_contributions_tax: 0 + income_tax: 1486.0 + +- name: Trading losses do not offset statutory employment pay for relief + period: 2026 + absolute_error_margin: 0.01 + input: + age: 60 + employment_income: 20000 + statutory_sick_pay: 2000 + self_employment_income: -5000 + employee_pension_contributions_reported: 25000 + output: + pension_contributions_relief: 22000 + +# Supplied ANI is an explicit input contract. Restore the same actual pension +# relief for equal total relevant earnings, regardless of the SSP wage split. +- name: Supplied ANI all-wages annual allowance control + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 50 + employment_income: 22000 + statutory_sick_pay: 0 + statutory_maternity_pay: 0 + self_employment_income: 0 + employee_pension_contributions_reported: 22000 + personal_pension_contributions: 0 + employer_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + adjusted_net_income: 5000 + output: + employment_benefits: 0 + pension_contributions_relief: 22000 + net_income: 27000 + pension_contributions_for_annual_allowance: 22000 + pension_annual_allowance_threshold_income: 5000 + pension_annual_allowance_adjusted_income: 27000 + +- name: Supplied ANI SSP preserves annual allowance earnings base + period: 2026 + absolute_error_margin: 0.01 + input: + region: LONDON + age: 50 + employment_income: 20000 + statutory_sick_pay: 2000 + statutory_maternity_pay: 0 + self_employment_income: 0 + employee_pension_contributions_reported: 22000 + personal_pension_contributions: 0 + employer_pension_contributions: 0 + pension_contributions_via_salary_sacrifice: 0 + adjusted_net_income: 5000 + output: + employment_benefits: 2000 + pension_contributions_relief: 22000 + net_income: 27000 + pension_contributions_for_annual_allowance: 22000 + pension_annual_allowance_threshold_income: 5000 + pension_annual_allowance_adjusted_income: 27000 diff --git a/policyengine_uk/utils/pensions.py b/policyengine_uk/utils/pensions.py index 754fd23a2d..4c9b81729c 100644 --- a/policyengine_uk/utils/pensions.py +++ b/policyengine_uk/utils/pensions.py @@ -3,6 +3,19 @@ from policyengine_uk.utils.supplied_inputs import supplied_input +def relevant_uk_earnings(person, period): + """FA 2004 s.189 earnings, including employer-paid taxable SSP/SMP. + + Statutory employment pay is held separately from wages. A trading loss + does not reduce those employment earnings (HMRC PTM044100). + """ + return ( + person("employment_income", period) + + person("employment_benefits", period) + + np.maximum(0, person("self_employment_income", period)) + ) + + def pension_contributions_relief_amount( contributions, relevant_uk_earnings, ras_contributions, age, hmrc ): @@ -39,11 +52,7 @@ def annual_allowance_net_income(person, period, parameters): returned = person("salary_sacrifice_returned_to_income", period) hmrc = parameters(period).gov.hmrc actual_member_contributions = person("pension_contributions", period) - returned - actual_earnings = ( - person("employment_income", period) - - returned - + np.maximum(0, person("self_employment_income", period)) - ) + actual_earnings = relevant_uk_earnings(person, period) - returned supplied_ani = supplied_input(person, "adjusted_net_income", period) if supplied_ani is not None: # Supplied ANI already determines net income, including its losses. diff --git a/policyengine_uk/variables/gov/hmrc/pensions/pension_annual_allowance_threshold_income.py b/policyengine_uk/variables/gov/hmrc/pensions/pension_annual_allowance_threshold_income.py index 95f4db1d41..374353361c 100644 --- a/policyengine_uk/variables/gov/hmrc/pensions/pension_annual_allowance_threshold_income.py +++ b/policyengine_uk/variables/gov/hmrc/pensions/pension_annual_allowance_threshold_income.py @@ -2,6 +2,7 @@ from policyengine_uk.utils.pensions import ( annual_allowance_net_income, pension_contributions_relief_amount, + relevant_uk_earnings, ) @@ -37,11 +38,7 @@ def formula(person, period, parameters): # actual member contributions. Its relief need not equal the returned # amount: after 75 there is none, and low earnings can use basic RAS. actual_member_contributions = person("pension_contributions", period) - returned - actual_earnings = ( - person("employment_income", period) - - returned - + max_(0, person("self_employment_income", period)) - ) + actual_earnings = relevant_uk_earnings(person, period) - returned relief = where( returned > 0, pension_contributions_relief_amount( diff --git a/policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_relief.py b/policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_relief.py index 8fdf23d848..d9afcef026 100644 --- a/policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_relief.py +++ b/policyengine_uk/variables/gov/hmrc/pensions/pension_contributions_relief.py @@ -1,5 +1,8 @@ from policyengine_uk.model_api import * -from policyengine_uk.utils.pensions import pension_contributions_relief_amount +from policyengine_uk.utils.pensions import ( + pension_contributions_relief_amount, + relevant_uk_earnings, +) class pension_contributions_relief(Variable): @@ -14,6 +17,8 @@ class pension_contributions_relief(Variable): "above earnings (s. 191(7)); the model takes personal pension " "contributions as relief at source and employee contributions as a " "net pay arrangement. The annual allowance does not limit relief. " + "Relevant employment earnings include employer-paid taxable statutory " + "pay through employment_benefits. " "Contributions above it can be relieved here and separately attract " "the annual allowance charge (personal_pension_contributions_tax) " "under s. 227. The charge need not equal their tax relief." @@ -36,21 +41,19 @@ class pension_contributions_relief(Variable): title="Finance Act 2004 s. 191", href="https://www.legislation.gov.uk/ukpga/2004/12/section/191", ), + dict( + title="HMRC PTM044100: relevant UK earnings", + href="https://www.gov.uk/hmrc-internal-manuals/pensions-tax-manual/ptm044100", + ), ] unit = GBP def formula(person, period, parameters): hmrc = parameters(period).gov.hmrc contributions = person("pension_contributions", period) - # Relevant UK earnings (s. 189(2)) are employment income and trading - # income. A trading loss gives no relevant earnings; it does not - # reduce employment income. - relevant_uk_earnings = person("employment_income", period) + max_( - 0, person("self_employment_income", period) - ) return pension_contributions_relief_amount( contributions, - relevant_uk_earnings, + relevant_uk_earnings(person, period), person("personal_pension_contributions", period), person("age", period), hmrc,