Skip to content

Include SSP and SMP in pension-relief earnings - #2464

Open
juaristi22 wants to merge 2 commits into
mainfrom
fix-2371-statutory-pay-pension-relief
Open

juaristi22 wants to merge 2 commits into
mainfrom
fix-2371-statutory-pay-pension-relief

Conversation

@juaristi22

@juaristi22 juaristi22 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

Addresses #2371.

Count employer-paid, taxable statutory sick and maternity pay once in the relevant UK earnings used for pension contribution relief and the annual-allowance calculations. A common earnings helper keeps the relief, adjusted-income restoration and threshold-income paths consistent after the pension changes merged in #2441.

The new paired regression uses £22,000 relevant earnings, £22,000 net-pay contributions and supplied adjusted net income of £5,000. Replacing £2,000 of wages with statutory sick pay must preserve £27,000 restored income and a £5,000 threshold-income result. The unchanged integration produced £3,000 for the SSP case; both cases now pass. Supplied ANI is an explicit input in these tests.

Reproducer: policyengine_uk/tests/policy/baseline/gov/hmrc/pensions/statutory_pay_pension_relief.yaml

Sources: FA 2004 s189 and HMRC PTM044100, which expressly includes employer-paid SSP/SMP. Tests use actual annual taxable payments.

Validation on the candidate:

  • Genuine numerical RED before the helper fix, followed by 12 passing focused cases; the original 10 cases and README remain unchanged.
  • All 127 pension policy cases passed with minimum Core 3.32.27, including the focused cases.
  • Frozen Core 3.33.2: all 3,587 policy YAML cases passed. The complete Python run recorded 6,512 passed, 96 skipped and one expected failure; its original audit rejected two environment skips.
  • The coverage case then passed with CI's sys.monitoring configuration. After independent qualification of the single Darwin/glibc skip, the native auditor passed a RUN-only local composition of all 6,609 outcomes: 6,513 passed, 95 skipped and one expected failure. The tracked allowlist and original audit remain unchanged; hosted Linux CI is separate.
  • Formatting, Ruff and diff checks passed without changing tested source bytes. Independent captured-head policy and code review completed with no findings or material gaps; fix verification is clean for all five plan items. Hosted checks on the pushed head remain a separate gate.

Issue #2371 remains open: statutory paternity pay needs #2368 to add SPP to the taxable aggregate. This change preserves the loss-relief helper's existing taxable-pay input, which already includes statutory pay. PR #2462 will be based on this verified change to preserve its separate NI-only and actual-return input contract. The external #2368 and #2386 branches remain separate landing work. Private population effects have not been calculated.

axiom: needed

@juaristi22 juaristi22 self-assigned this Oct 16, 2026

@vahid-ahmadi vahid-ahmadi left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fine once the merged annual-allowance helpers use the same relevant earnings.

@juaristi22 the law and the main change are right. PTM044100 lists "Statutory Sick Pay (SSP) and Statutory Maternity Pay (SMP) if paid by the employer and taxable under Section 7(2) ITEPA 2003" as relevant UK earnings, and employment_benefits is exactly SSP + SMP on main, so it is counted once. One gap appears after merging with current main.

  1. must The branch is 138 commits behind main, and #2441 has since merged three more relevant-earnings constructions that leave out employment_benefits: policyengine_uk/utils/pensions.py:42-46 (actual_earnings, used on the supplied-ANI path at :57 and for relievable_net_pay at :66-67) and policyengine_uk/variables/gov/hmrc/pensions/pension_annual_allowance_threshold_income.py:40-44. Git merges the PR cleanly, so nothing flags it. On the merged tree, a 50-year-old with £22,000 net-pay contributions and supplied adjusted_net_income £5,000 gets threshold income £5,000 and adjusted income £27,000 when all £22,000 earnings are wages, but £3,000 and £25,000 when £2,000 of it is SSP. The helper adds back £20,000 relief while pension_contributions_relief now gives £22,000. The PR body says the #2441 combination kept the aggregate at both callers, but this branch does not carry that change. Please rebase and add employment_benefits at those sites (a shared relevant_uk_earnings helper would stop this drifting again), with one SSP case for the supplied-ANI path.
  2. should #2462 rewrites the same actual_earnings block in utils/pensions.py (the actual_earnings lines). Whichever lands second will need to keep employment_benefits in it.
  3. nit changelog.d/2371.fixed.md: "taxable statutory employment pay" reads wider than the change, which covers SSP and SMP only until #2368 adds SPP. "statutory sick and maternity pay" would be exact.
  4. nit An open docs PR (#2386, docs/book/programs/gov/hmrc/pension-tax-relief.md) describes the cap as "employment plus self-employment income". That line will be out of date once this lands.

What I checked

  • Read the PR body, full diff and #2371. Checked FA 2004 s.189 on legislation.gov.uk and PTM044100 on GOV.UK.
  • Hand-computed five YAML expectations, e.g. "SSP final ordinary tax": (20,000 + 2,000 + 20,000 - 22,000 - 12,570) x 20% = £1,486; before the fix relief is £20,000, giving £1,886.
  • policyengine-core test on the new file: 10 passed. Whole tests/policy/baseline/gov/hmrc/pensions folder: 81 passed. With the formula reverted to the merge base: 5 failed, 5 passed, as the PR says.
  • Merged origin/main locally (no conflicts) and ran the item 1 households with Simulation.
  • ruff check and ruff format --check on the changed Python file: clean. CI on the head: all checks pass. The Reproducer: and axiom: lines are present and Fix evidence passed.
  • Not checked: the full test suite locally, or the population effect on enhanced FRS data.

🤖 Generated with Claude Code

vahid-ahmadi added a commit that referenced this pull request Oct 18, 2026
The page said the earnings limit applies first, so someone with no
earnings gets no relief, and that the annual allowance caps relief. On
main, relief at source can use the £3,600 basic amount above earnings
(FA 2004 s. 191(7)), the annual allowance does not limit relief, the
taper tests adjusted and threshold income, the charge is sliced across
the rate bands, and relieved contributions reduce adjusted net income.
Mentions #2464 (SSP and SMP in relevant earnings) and #2488 (ATI uplift)
as pending. Figures re-run on policyengine-uk 2.135.6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@juaristi22
juaristi22 force-pushed the fix-2371-statutory-pay-pension-relief branch from 3f0b0b0 to 4d4787e Compare October 18, 2026 16:29
@juaristi22

Copy link
Copy Markdown
Collaborator Author

Rebased this onto the pension changes merged in #2441 and aligned the three relevant-earnings paths through one helper that includes taxable employer-paid SSP/SMP once.

The paired supplied-ANI regression now preserves threshold income of £5,000 and adjusted income of £27,000 for both £22,000 wages and £20,000 wages plus £2,000 SSP, with £22,000 net-pay contributions. The SSP threshold was £3,000 before the fix. The original ten cases and README are unchanged, and the changelog now names SSP/SMP explicitly.

Validation: 127 pension cases passed on minimum Core 3.32.27; 3,587 policy YAML cases passed on frozen Core 3.33.2. The full Python run recorded 6,512 passes, 96 skips and one expected failure. Its coverage-only follow-up passed under CI's sys.monitoring configuration; an independently qualified local Darwin/glibc exception then allowed the unchanged native auditor to account for all 6,609 outcomes in a separate local composition. The original failed audit is retained and hosted Linux CI remains separate.

#2371 remains open for SPP/#2368. The #2462 follow-up is planned to stack on this change so the shared helper and its relief/threshold callers remain coherent. The external #2386 cap wording still needs a landing-time reconciliation with that work; its branch has not been changed here.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants