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/2454.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Kept the pension salary-sacrifice cap in National Insurance earnings without inflating ordinary pay or member pension contributions, retained actual supplied ordinary returns in the annual-allowance calculations, and preserved the childcare income tests in #2173.
46 changes: 23 additions & 23 deletions docs/engineering/pension-annual-allowance.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,11 @@ is deducted separately through `allowances`. Employee contributions represent
net pay and personal contributions represent gross relief at source (RAS).
Both annual-allowance income tests use
[`annual_allowance_net_income`](../../policyengine_uk/utils/pensions.py).
This shared helper reconstructs the model's net-income convention from actual
income, before separately modeled pension relief, excluding the NICs-only
salary-sacrifice return. Accordingly:
This shared helper retains the model's net-income convention before separately
modeled pension relief and applies net-pay/trading-loss ordering to actual
income and member payments. The NICs-only excess never enters these amounts;
a positive supplied legacy ordinary return is actual pay/member payment.
Accordingly:

- `pension_annual_allowance_adjusted_income` adds the total pension input
amount to that reconstructed income, then subtracts the individual's actual
Expand Down Expand Up @@ -69,20 +71,19 @@ prevents Step 2 relief from exceeding the remaining income. This limit
applies in every model year, including before the NICs cap begins in 2029.
It can therefore change pre-2029 allowances for people whose trading-loss
relief is limited by income and who make relievable net-pay contributions.
Removing synthetic pay after deducting the
original relief would retain relief calculated on income the person did not
receive. Threshold income also recomputes [FA 2004 ss. 188–191 pension
The helper replaces the original loss with the loss allowed after relievable
net pay. Threshold income deducts [FA 2004 ss. 188–191 pension
relief](https://www.legislation.gov.uk/ukpga/2004/12/section/188) on actual
earnings and contributions.

An explicitly supplied `adjusted_net_income` is authoritative. For this input
path, the helper restores gross Gift Aid and pension relief recomputed on
actual contributions and earnings. It does not remove returned pay or
reconstruct trading-loss relief: the supplied ANI contains neither synthetic
pay nor relief recalculated by the NICs reclassification. The annual-allowance
base is therefore independent of the NICs cap on this path too.
actual contributions and earnings. It does not remove an actual supplied
ordinary return or reconstruct trading-loss relief: the supplied ANI already
determines the income after those losses. The annual-allowance base is
independent of the NICs cap on this path too.

Other modeled deductions do not depend on the returned pay: brought-forward
Other modeled deductions retain their existing treatment: brought-forward
trade losses, capital allowances and the trading allowance depend on trading
profits. Property finance-cost relief is a downstream tax reducer. Gift Aid's
adjusted-net-income deduction and rate-limit extension do not reduce either
Expand Down Expand Up @@ -124,24 +125,23 @@ this correction does not reconstruct DB rights, overseas adjustments, unused
allowance histories or anti-avoidance arrangements under s. 228ZB.

[The NICs Act 2026](https://www.legislation.gov.uk/ukpga/2026/15/enacted)
changes NICs treatment without changing s. 228ZA. From 2029, the model moves
sacrifice above the NICs exemption cap into
employee contributions and employment income. The AA calculations remove
that synthetic return from both pay and member contributions and recompute
income-dependent relief as described above. Pension relief preserves the
changes NICs treatment without changing s. 228ZA. From 2029, the model includes
sacrifice above the NICs exemption cap only in Class 1 earnings through
`salary_sacrifice_pension_ni_excess`. It does not create ordinary pay or member
contributions. The legacy `salary_sacrifice_returned_to_income` defaults to
zero; a positive explicit input represents actual ordinary pay/member payment
and is retained in the AA calculations. Pension relief preserves the
basic RAS amount even when cash earnings are below £3,600 and gives no member
relief from age 75. The DC input keeps the full employer sacrifice at every
age. The threshold-income add-back uses the full
sacrifice before this reclassification. The separate employer-cost wage
sacrifice before the NICs cap. The separate employer-cost wage
response can still change income; it is held fixed when testing the cap's
statutory effect.

Cap invariance covers the two taper incomes, pension input and allowance.
Charge comparisons additionally hold ordinary income-tax inputs fixed: the
separate ANI and personal-allowance interaction can change the marginal tax
charged on the same excess. Ordinary income tax retains its existing
reclassification behavior; [#2454](https://github.com/PolicyEngine/policyengine-uk/issues/2454)
tracks that separate defect.
With the separate wage response held fixed, the cap does not change ordinary
pay, member payments, ANI or the personal allowance. Cap-invariance checks
cover those paths alongside the two taper incomes, pension input, allowance
and charge. Actual NI liability changes through the existing Class 1 curves.

The taxable death-benefit and grandfathered-sacrifice inputs default to zero.
Pension relief retains the model's aggregate earnings cap and allocation
Expand Down
31 changes: 31 additions & 0 deletions docs/engineering/salary-sacrifice-nics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Salary sacrifice and the NICs cap

`employment_income` supplies cash pay after contractual salary sacrifice, and
`pension_contributions_via_salary_sacrifice` supplies the employer contribution
separately. UKSimulation preserves this existing input convention; the model
does not deduct that sacrifice a second time from cash pay.

The cap limits NIC exemption, rather than returning ordinary pay or creating a
member pension payment. `salary_sacrifice_pension_ni_excess` adds the excess once
to `ni_class_1_income`. Full actual sacrifice still counts once in the current
DC pension-input proxy. The capped `..._adjusted` measure is the NIC-exempt
amount. The two NI reporting measures remain approximations; actual liability
uses the Class 1 curves and age flags.

The legacy `salary_sacrifice_returned_to_income` defaults to zero. Explicit
ordinary-return inputs retain their previous pay/member-payment semantics.
Actual reported member payments and the separate broad-base haircut assumption
are unchanged. The merged #2441 age-75 member accounting and annual-allowance
taper rules are retained: member payments before age 75, employer payments and
full sacrifice enter the default DC input, while member payments from age 75
are excluded. A positive supplied ordinary return is actual pay and a member
payment in these readers too; it is not removed as an unreceived NICs return.
The AA helper still applies the net-pay/trading-loss ordering, RAS earnings and
age limits, supplied-ANI restoration and statutory pension-input overrides.

The [NICs Act 2026](https://www.legislation.gov.uk/ukpga/2026/15/enacted)
provides the contributions-law machinery for 2029–30 onwards. The existing
2029-04-06 parameter and annual sampling are retained. This is not a complete
implementation of future payroll-interval regulations. Employer contributions
remain subject to [ITEPA 2003 s308](https://www.legislation.gov.uk/ukpga/2003/1/section/308);
member relief separately follows [FA 2004 ss188–191](https://www.legislation.gov.uk/ukpga/2004/12/section/188).
2 changes: 1 addition & 1 deletion policyengine_uk/data/fiscal_partition.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ exclusions:
- {variable: ni_class_3, kind: component, head: nics_individual}
- {variable: ni_class_4, kind: component, head: nics_individual}
- {variable: ni_class_1_employer, kind: component, head: nics_employer}
- {variable: salary_sacrifice_pension_ni_employee, kind: measure_within, head: nics_individual, note: "The excess sacrifice is added back to employment_income, so Class 1 already charges it."}
- {variable: salary_sacrifice_pension_ni_employee, kind: measure_within, head: nics_individual, note: "The excess sacrifice is added back to ni_class_1_income, so Class 1 already charges it."}
- {variable: salary_sacrifice_pension_ni_employer, kind: measure_within, head: nics_employer, note: "As above, for employer Class 1."}
- {variable: tax, kind: composite, spans: [income_tax, nics_individual]}
- {variable: benunit_tax, kind: composite, spans: [income_tax, nics_individual]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@
This tests the £2,000 salary sacrifice cap policy announced in Autumn Budget 2025,
which takes effect from April 2029.

Methodology (matching blog at https://policyengine.org/uk/research/uk-salary-sacrifice-cap):
- Employees redirect excess above £2,000 to regular pension contributions
- Regular pension contributions get income tax relief but NOT NI relief
Current NI-only model routing (historical population benchmarks below):
- Excess above £2,000 is NIC-only earnings; pension contributions stay employer payments
- Ordinary income-tax treatment is unchanged; only NIC exemption is capped
- Broad-base haircut: employers spread NI costs across ALL workers (~0.16% of employment income)
- Revenue comes from NI on the redirected excess
- Revenue comes from NI on the excess included in NI earnings

Expected revenue: ~£3.3 billion in 2029-30

Expand Down Expand Up @@ -60,10 +60,10 @@ def test_salary_sacrifice_cap_revenue_impact(baseline_simulation, reform_simulat
"""
Test that the £2,000 salary sacrifice cap raises ~£3.3 billion.

This matches the PolicyEngine blog methodology:
- Employees redirect excess to regular pension contributions
- Full excess subject to NI (broad-base haircut reduces all workers' income)
- Income tax relief preserved via regular pension contributions
The current NI-only model routing is:
- Excess is included only in NI earnings
- Broad-base haircut reduces all workers' income as a separate assumption
- Employer pension contributions retain their income-tax treatment
"""
baseline_gov_balance = baseline_simulation.calculate(
"gov_balance", POLICY_YEAR
Expand Down Expand Up @@ -95,7 +95,7 @@ def test_ni_increases_with_reform(baseline_simulation, reform_simulation):
"""
Test that total NI increases when the cap is applied.

The reform adds excess salary sacrifice back to employment income,
The reform adds excess salary sacrifice only to NIC earnings,
which is then subject to NI through the normal Class 1 calculation.
"""
baseline_ni = baseline_simulation.calculate(
Expand Down Expand Up @@ -127,9 +127,8 @@ def test_income_tax_impact(baseline_simulation, reform_simulation):
"""
Test the income tax impact of the reform.

The excess is added to employment income and to employee pension
contributions for relief. Due to pension relief caps, some people
don't get full relief, resulting in a small positive income tax impact.
The excess is NIC-only and creates no member payment or ordinary tax.
The separate broad-base haircut can still affect ordinary tax.
"""
baseline_tax = baseline_simulation.calculate("income_tax", POLICY_YEAR).sum()
reform_tax = reform_simulation.calculate("income_tax", POLICY_YEAR).sum()
Expand All @@ -141,25 +140,22 @@ def test_income_tax_impact(baseline_simulation, reform_simulation):
print(f"Income tax change: £{tax_change / 1e9:.3f}bn")

# Income tax change should be small and approximately neutral.
# The reform redirects excess salary sacrifice to employee pension
# contributions (which get income tax relief), while the total pension
# input for Annual Allowance purposes stays the same (no AA charge
# difference). Net effect is a small income tax decrease.
# NIC reclassification preserves actual pension input and ordinary pay.
# The unchanged broad-base haircut remains a separate economic effect.
assert abs(tax_change) < 1e9, (
f"Income tax change should be small (<£1bn), got £{tax_change / 1e9:.3f}bn"
)


@pytest.mark.microsimulation
def test_excess_redirected_to_pension(reform_simulation):
def test_excess_included_in_ni_earnings(reform_simulation):
"""
Test that full excess is redirected to employee pension contributions.
Test that full excess is included in NIC earnings.

The blog assumes employees maintain their total pension contributions
by redirecting the full excess to regular pension contributions.
Physical sacrifice remains an employer pension contribution.
"""
redirected = reform_simulation.calculate(
"salary_sacrifice_returned_to_income", POLICY_YEAR
"salary_sacrifice_pension_ni_excess", POLICY_YEAR
).sum()

# Should be significant (blog says £13.8bn excess - updated to current model)
Expand Down Expand Up @@ -215,12 +211,11 @@ def test_affected_population(reform_simulation):


@pytest.mark.microsimulation
def test_full_excess_redirected(reform_simulation):
def test_full_excess_included_in_ni_earnings(reform_simulation):
"""
Test that the full excess is redirected (no targeted haircut).
Test that the full excess is included in NIC earnings (no targeted haircut).

The broad-base haircut reduces ALL workers' employment income,
but the full excess above cap is redirected to regular pension contributions.
The separate broad-base haircut still reduces ALL workers' employment income.
"""
# Get weighted totals using map_to for proper aggregation. Only people
# with pay have a salary sacrifice, so the excess is measured on that.
Expand All @@ -231,7 +226,7 @@ def test_full_excess_redirected(reform_simulation):
)
weights = reform_simulation.calculate("person_weight", POLICY_YEAR)
redirected = reform_simulation.calculate(
"salary_sacrifice_returned_to_income", POLICY_YEAR, map_to="person"
"salary_sacrifice_pension_ni_excess", POLICY_YEAR, map_to="person"
)

cap = 2000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
# £100 excess * 8% = £8
salary_sacrifice_pension_ni_employee: 100 * 0.08

- name: Employee NI on full redirected excess
- name: Employee NI on full NIC-only excess
period: 2025
absolute_error_margin: 0.01
input:
Expand All @@ -103,8 +103,9 @@
output:
# £1,000 excess * 8% = £80
salary_sacrifice_pension_ni_employee: (3_000 - 2_000) * 0.08
# Full excess is redirected (no targeted haircut)
salary_sacrifice_returned_to_income: 1_000
# Full excess enters NIC earnings (no targeted haircut)
salary_sacrifice_pension_ni_excess: 1_000
salary_sacrifice_returned_to_income: 0

- name: Broad-base haircut reduces all workers employment income
period: 2025
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
# £3,000 excess * 15% = £450
salary_sacrifice_pension_ni_employer: (5_000 - 2_000) * 0.15

- name: Employer NI on full redirected excess
- name: Employer NI on full NIC-only excess
period: 2025
absolute_error_margin: 0.01
input:
Expand All @@ -103,5 +103,6 @@
output:
# £1,000 excess * 15% = £150
salary_sacrifice_pension_ni_employer: (3_000 - 2_000) * 0.15
# Full excess is redirected (no targeted haircut)
salary_sacrifice_returned_to_income: 1_000
# Full excess enters NIC earnings (no targeted haircut)
salary_sacrifice_pension_ni_excess: 1_000
salary_sacrifice_returned_to_income: 0
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,11 @@
pension_annual_allowance_threshold_income: 220_000
pension_annual_allowance: 40_000

# The NICs cap redirects 28,000 to employee contributions and the broad-base
# haircut reduces pay by 280. Threshold income still adds back all 30,000:
# 202,720 - 28,000 + 30,000 = 204,720. Pension input remains 130,000,
# adjusted income is 202,720 + 130,000 - 28,000 = 304,720, and the charge
# is (130,000 - 37,640) * 45% = 41,562.
# The unchanged broad-base haircut reduces actual pay by 280. The 28,000
# NICs excess creates neither ordinary pay nor a member payment. Threshold
# income still adds back all 30,000: 174,720 + 30,000 = 204,720. Pension
# input remains 130,000, adjusted income is 174,720 + 130,000 = 304,720,
# and the charge is (130,000 - 37,640) * 45% = 41,562.
- name: The 2029 NICs cap does not limit the salary sacrifice threshold income addback
period: 2029
input:
Expand All @@ -254,8 +254,10 @@
pension_contributions_via_salary_sacrifice: 30_000
employer_pension_contributions: 100_000
output:
employment_income: 202_720
pension_contributions_relief: 28_000
employment_income: 174_720
pension_contributions_relief: 0
salary_sacrifice_pension_ni_excess: 28_000
ni_class_1_income: 202_720
pension_contributions_for_annual_allowance: 130_000
pension_annual_allowance_threshold_income: 204_720
pension_annual_allowance_adjusted_income: 304_720
Expand Down
Loading
Loading