Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/2371.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Included statutory sick and maternity pay in the relevant earnings limits for pension contribution relief and annual-allowance income tests.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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
19 changes: 14 additions & 5 deletions policyengine_uk/utils/pensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
):
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from policyengine_uk.utils.pensions import (
annual_allowance_net_income,
pension_contributions_relief_amount,
relevant_uk_earnings,
)


Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
@@ -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):
Expand All @@ -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."
Expand All @@ -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,
Expand Down
Loading