Skip to content

Commit 0fc7ea1

Browse files
MaxGhenisclaude
andauthored
fix: include employer contributions in Annual Allowance calculation (#1503)
* fix: include employer contributions in Annual Allowance calculation Per Finance Act 2004 s.233, the pension input amount for money purchase arrangements includes both individual and employer contributions. Previously only employee + personal contributions were counted, understating the Annual Allowance tax charge. Adds pension_contributions_for_annual_allowance variable that sums all contribution types without modifying pension_contributions (which feeds means-tested benefit calculations). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fix: update microsimulation tests for corrected AA calculation The salary sacrifice income tax test previously expected income tax to increase with the cap reform, but that was due to the AA bug (excess counted toward AA in reform but not baseline). Now total pension input for AA is the same in both scenarios, so the income tax effect is approximately neutral. Update UC taper rate expected impact from -44.8 to -43.2 to reflect the baseline shift from employer contributions now counting toward the AA. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Add changelog entry and use underscore thousands separators in YAML Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * fixup! Add changelog entry and use underscore thousands separators in YAML * Update UC taper reform expected impact for corrected AA parameters The AA parameter fix (£40k→£60k from 2023-24) shifts the UC taper reform impact from -43.2bn to -34.6bn, because more pension contributions now fall within the allowance. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix highest_education KeyError in student loan uprating The guard added in #1500 only checked for student_loan_plan but the function also requires highest_education, which is missing from the current enhanced FRS dataset on HuggingFace. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Revert UC taper expected impact to -43.2bn The -34.6bn value was computed locally with monkey-patched student loan uprating (skipping highest_education). CI uses the actual HF dataset which lacks highest_education, so student loan uprating is skipped via the guard, producing a different baseline. The CI-observed value is still -43.2bn. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent dab780c commit 0fc7ea1

13 files changed

Lines changed: 233 additions & 31 deletions

File tree

CLAUDE.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# Development Guidelines for PolicyEngine UK
22

3-
## Build Commands
3+
## Git workflow
4+
- **Always branch from `main`**: `git checkout main && git pull origin main && git checkout -b your-branch`
5+
- Default branch is `main` (not `master`)
6+
7+
## Build commands
48
- Install: `make install` or `pip install -e ".[dev]" --config-settings editable_mode=compat`
59
- Format code: `make format` or `black . -l 79`
610
- Run all tests: `make test`

changelog_entry.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
- bump: minor
2+
changes:
3+
fixed:
4+
- Include employer pension contributions and salary sacrifice in the Annual Allowance tax charge calculation, per Finance Act 2004 s.233.

policyengine_uk/data/economic_assumptions.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,10 @@ def uprate_student_loan_plans(
245245
For (2) and (3), we use highest_education == TERTIARY as the signal
246246
for who is a graduate, then apply a flat take-up probability.
247247
"""
248-
# Skip if student_loan_plan column doesn't exist yet (e.g., during
248+
# Skip if required columns don't exist yet (e.g., during
249249
# initial dataset creation before imputation runs)
250-
if "student_loan_plan" not in current_year.person.columns:
250+
required = ("student_loan_plan", "highest_education")
251+
if any(col not in current_year.person.columns for col in required):
251252
return current_year
252253

253254
year = int(current_year.time_period)
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
description: Annual allowance for relief on pension contributions
22
values:
33
2015-04-01: 40_000
4+
2023-04-06: 60_000
45
metadata:
56
unit: currency-GBP
67
label: Annual allowance for pension contributions
7-
reference:
8+
reference:
9+
- title: Finance Act 2004 s. 228
10+
href: https://www.legislation.gov.uk/ukpga/2004/12/section/228
811
- title: Tax on your private pension contributions
912
href: https://www.gov.uk/tax-on-your-private-pension/annual-allowance
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
description: Minimum annual allowance for relief on pension contributions
22
values:
33
2015-04-01: 4_000
4+
2023-04-06: 10_000
45
metadata:
56
unit: currency-GBP
67
label: Minimum annual allowance for pension contributions
7-
reference:
8+
reference:
9+
- title: Finance Act 2004 s. 228ZA
10+
href: https://www.legislation.gov.uk/ukpga/2004/12/section/228ZA
811
- title: Work out your reduced (tapered) annual allowance
912
href: https://www.gov.uk/guidance/pension-schemes-work-out-your-tapered-annual-allowance
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
description: Adjusted net income limit for tapering of the annual allowance
22
values:
33
2015-04-01: 240_000
4+
2023-04-06: 260_000
45
metadata:
56
unit: currency-GBP
67
label: Annual allowance taper threshold
7-
reference:
8+
reference:
9+
- title: Finance Act 2004 s. 228ZA
10+
href: https://www.legislation.gov.uk/ukpga/2004/12/section/228ZA
811
- title: Tax on your private pension contributions
912
href: https://www.gov.uk/tax-on-your-private-pension/annual-allowance

policyengine_uk/tests/microsimulation/test_salary_sacrifice_cap_reform.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,14 @@ def test_income_tax_impact(baseline_simulation, reform_simulation):
151151
print(f"Reform income tax: £{reform_tax/1e9:.3f}bn")
152152
print(f"Income tax change: £{tax_change/1e9:.3f}bn")
153153

154-
# Income tax should increase slightly (due to pension relief caps)
155-
# Expected to be around £1-2bn
154+
# Income tax change should be small and approximately neutral.
155+
# The reform redirects excess salary sacrifice to employee pension
156+
# contributions (which get income tax relief), while the total pension
157+
# input for Annual Allowance purposes stays the same (no AA charge
158+
# difference). Net effect is a small income tax decrease.
156159
assert (
157-
tax_change > 0
158-
), f"Income tax should increase, got £{tax_change/1e9:.3f}bn"
159-
assert (
160-
tax_change < 3e9
161-
), f"Income tax increase should be <£3bn, got £{tax_change/1e9:.3f}bn"
160+
abs(tax_change) < 1e9
161+
), f"Income tax change should be small (<£1bn), got £{tax_change/1e9:.3f}bn"
162162

163163

164164
@pytest.mark.microsimulation

policyengine_uk/tests/policy/baseline/gov/hmrc/income_tax/allowances/is_allowance_eligible.yaml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@
3030
- name: Annual Allowance for Pension Contributions
3131
period: 2024
3232
input:
33-
adjusted_net_income: 60000
34-
output:
35-
pension_annual_allowance: 40000.0
33+
adjusted_net_income: 60_000
34+
output:
35+
# AA = £60,000 from 2023-04-06 (FA 2004 s.228, amended by Finance (No. 2) Act 2023)
36+
# ANI of £60,000 is below the taper threshold of £260,000, so full AA applies
37+
pension_annual_allowance: 60_000
3638

3739
- name: Trading Allowance Deduction
3840
period: 2024
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Tests for pension_contributions_for_annual_allowance
2+
# Per Finance Act 2004 s.233, the pension input amount for money purchase
3+
# arrangements includes BOTH individual contributions AND employer contributions.
4+
# https://www.legislation.gov.uk/ukpga/2004/12/section/233
5+
6+
- name: No contributions
7+
period: 2024
8+
input:
9+
employee_pension_contributions_reported: 0
10+
personal_pension_contributions: 0
11+
employer_pension_contributions: 0
12+
pension_contributions_via_salary_sacrifice: 0
13+
output:
14+
pension_contributions_for_annual_allowance: 0
15+
16+
- name: Employee contributions only
17+
period: 2024
18+
input:
19+
employee_pension_contributions_reported: 5_000
20+
personal_pension_contributions: 0
21+
employer_pension_contributions: 0
22+
pension_contributions_via_salary_sacrifice: 0
23+
output:
24+
pension_contributions_for_annual_allowance: 5_000
25+
26+
- name: Personal contributions only
27+
period: 2024
28+
input:
29+
employee_pension_contributions_reported: 0
30+
personal_pension_contributions: 3_000
31+
employer_pension_contributions: 0
32+
pension_contributions_via_salary_sacrifice: 0
33+
output:
34+
pension_contributions_for_annual_allowance: 3_000
35+
36+
- name: Employer contributions only
37+
period: 2024
38+
input:
39+
employee_pension_contributions_reported: 0
40+
personal_pension_contributions: 0
41+
employer_pension_contributions: 10_000
42+
pension_contributions_via_salary_sacrifice: 0
43+
output:
44+
pension_contributions_for_annual_allowance: 10_000
45+
46+
- name: All contribution types (no salary sacrifice)
47+
period: 2024
48+
input:
49+
employee_pension_contributions_reported: 5_000
50+
personal_pension_contributions: 3_000
51+
employer_pension_contributions: 10_000
52+
pension_contributions_via_salary_sacrifice: 0
53+
output:
54+
pension_contributions_for_annual_allowance: 18_000
55+
56+
- name: With salary sacrifice (below cap, no excess)
57+
period: 2024
58+
input:
59+
employee_pension_contributions_reported: 5_000
60+
personal_pension_contributions: 0
61+
employer_pension_contributions: 2_000
62+
pension_contributions_via_salary_sacrifice: 8_000
63+
output:
64+
# employee_pension_contributions = 5_000 + 0 (no excess) = 5_000
65+
# pension_contributions_via_salary_sacrifice_adjusted = 8_000 (all below cap)
66+
# Total: 5_000 + 0 + 2_000 + 8_000 = 15_000
67+
pension_contributions_for_annual_allowance: 15_000
68+
69+
- name: High employer contributions exceeding annual allowance
70+
period: 2024
71+
input:
72+
employee_pension_contributions_reported: 10_000
73+
personal_pension_contributions: 5_000
74+
employer_pension_contributions: 50_000
75+
pension_contributions_via_salary_sacrifice: 0
76+
output:
77+
# Total: 10_000 + 5_000 + 50_000 = 65_000
78+
# This exceeds the 2024 AA of 40_000 (verified separately by AA tax charge test)
79+
pension_contributions_for_annual_allowance: 65_000
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Tests for personal_pension_contributions_tax (Annual Allowance charge)
2+
# Per Finance Act 2004 s.227 and s.233, the annual allowance charge applies
3+
# to total pension input amounts exceeding the annual allowance.
4+
# The pension input amount includes employer contributions (s.233(1)(b)).
5+
# https://www.legislation.gov.uk/ukpga/2004/12/section/227
6+
# https://www.legislation.gov.uk/ukpga/2004/12/section/233
7+
#
8+
# The pension annual allowance is £60,000 from 2023-04-06 (FA 2004 s.228,
9+
# as amended by Finance (No. 2) Act 2023).
10+
11+
- name: Contributions within annual allowance - no tax charge
12+
period: 2024
13+
absolute_error_margin: 0.01
14+
input:
15+
employment_income: 80_000
16+
employee_pension_contributions_reported: 10_000
17+
personal_pension_contributions: 5_000
18+
employer_pension_contributions: 5_000
19+
output:
20+
# Total for AA: 10_000 + 5_000 + 5_000 = 20_000, below 60_000 AA
21+
personal_pension_contributions_tax: 0
22+
23+
- name: Employee-only contributions exceeding AA
24+
period: 2024
25+
absolute_error_margin: 0.01
26+
input:
27+
employment_income: 100_000
28+
employee_pension_contributions_reported: 70_000
29+
output:
30+
# Total for AA: 70_000, excess = 70_000 - 60_000 = 10_000
31+
# pension_contributions_relief = min(100k, 70k) then min(70k, 60k) = 60_000
32+
# taxed_income = 100_000 - 12_570 - 60_000 = 27_430
33+
# Marginal rate at 27_430 = 20% (basic rate)
34+
# Tax charge: 10_000 * 0.2 = 2_000
35+
personal_pension_contributions_tax: 2_000
36+
37+
- name: Employer contributions push total over AA
38+
period: 2024
39+
absolute_error_margin: 0.01
40+
input:
41+
employment_income: 100_000
42+
employee_pension_contributions_reported: 20_000
43+
employer_pension_contributions: 50_000
44+
output:
45+
# Total for AA: 20_000 + 50_000 = 70_000, excess = 70_000 - 60_000 = 10_000
46+
# pension_contributions_relief = min(100k, 20k) = 20_000
47+
# taxed_income = 100_000 - 12_570 - 20_000 = 67_430
48+
# Marginal rate at 67_430 = 40% (higher rate)
49+
# Tax charge: 10_000 * 0.4 = 4_000
50+
personal_pension_contributions_tax: 4_000
51+
52+
- name: Only employer contributions exceed AA
53+
period: 2024
54+
absolute_error_margin: 0.01
55+
input:
56+
employment_income: 100_000
57+
employer_pension_contributions: 70_000
58+
output:
59+
# Total for AA: 70_000 (all employer), excess = 70_000 - 60_000 = 10_000
60+
# pension_contributions_relief = 0 (no employee/personal contributions)
61+
# taxed_income = 100_000 - 12_570 = 87_430
62+
# Marginal rate at 87_430 = 40% (higher rate)
63+
# Tax charge: 10_000 * 0.4 = 4_000
64+
personal_pension_contributions_tax: 4_000

0 commit comments

Comments
 (0)