Skip to content

Annualise the employer NI secondary threshold to the statutory £5,000 - #2165

Merged
vahid-ahmadi merged 1 commit into
mainfrom
fix-secondary-threshold-annual-5000
Oct 7, 2026
Merged

vahid-ahmadi merged 1 commit into
mainfrom
fix-secondary-threshold-annual-5000

Conversation

@vahid-ahmadi

@vahid-ahmadi vahid-ahmadi commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #1967

Summary

From 2025-26, gov.hmrc.national_insurance.class_1.thresholds.secondary_threshold held the rounded weekly £96 from SI 2001/1004 reg 10(d). ni_class_1_employer multiplies it by WEEKS_IN_YEAR (52), so the model's annual threshold was £4,992. For an annual earnings period, the law gives £5,000:

"(3A) … the prescribed equivalents of the secondary threshold shall be— (a) where the earnings period is a month, £417; (b) where the earnings period is a year, £5,000; (c) where the earnings period is a multiple of a week, the amount calculated by dividing the figure in sub-paragraph (b) by 52 …" (SI 2001/1004 reg 11(3A))

The primary threshold and upper earnings limit already use this convention (12,570 / 52 and 50,270 / 52). docs/book/policy/model-baseline.md already says the threshold was reduced "to £5,000 annually". Before 2025-26 the two conventions agree (£175 × 52 = £9,100).

Change

  • secondary_threshold.yaml: the values from 2025-04-06 to 2030-04-06 are now 96.153846 (= 5,000 / 52). The 2025-04-06 entry now also cites reg 11(3A)(b), and the header comment explains the convention. The value is a single number, so CPI uprating from 2031-32 now starts from £5,000 a year instead of £4,992.
  • Display: the parameter keeps period: week, so it will now show as £96.15 a week (5,000 / 52) instead of the statutory reg 10(d) £96. The primary threshold already shows a derived weekly figure (£241.73) in the same way. The other option, keeping £96 and having the formula read an annual £5,000, was not taken because Employer NI annualises the secondary threshold as 52 × £96 = £4,992, not the statutory £5,000 #1967 specifies the annual / 52 convention.
  • No formula changes. ni_class_1_employer and the three contrib employer-NI incidence variables (adjusted_employer_cost, baseline_employer_cost, employer_ni_fixed_employer_cost_change) all multiply the same parameter by 52, so they now use £5,000 too.
  • Changelog: changelog.d/1967.fixed.md.

Impact

For an employee with fixed annual earnings of £5,000 or more, employer NI liability is £1.20 a year lower than under the old £4,992 threshold (15% × £8). Example: at £40,000 in 2026-27, employer NI is £5,250.00, not £5,251.20. Between £4,992 and £5,000 the difference is smaller (15% of the earnings above £4,992).

Aggregate check on the drill-2 dataset (microcosm_uk_2024_25.h5, SHA-256 aa31bdf6…), weighted totals, model years 2026 and 2027. Each run compares this branch (£5,000) with the same code plus a reform restoring £96 a week (£4,992 a year) from 2025-04-06.

Fixed gross earnings. Both runs set all three employer NI incidence parameters to 0: gov.contrib.policyengine.employer_ni.employee_incidence, consumer_incidence and capital_incidence. Gross employment income is identical in the two runs (largest per-person difference: £0).

Model year Employees liable for employer NI (£5,000 threshold) Change in ni_class_1_employer Change in gov_balance
2026 30.62m −£36.7m −£36.7m
2027 30.77m −£36.9m −£36.9m

At fixed earnings nothing else moves, so gov_balance changes by the same amount as employer NI. The change is £1.20 times the number of employees liable, plus a negligible amount from the £4,992–£5,000 band (about 150 weighted employees in 2026 and 7,100 in 2027). The model holds one employment per person, so these are counts of people.

Wage-adjusted comparison (default incidence). The first version of this description reported −£32.0m (2026) and −£32.1m (2027). Those figures used the default settings (employee_incidence = 1), so the £96 reform also lowered gross wages to keep employer cost fixed. They are not a fixed-earnings comparison. For reference, under the default settings:

Model year Change in ni_class_1_employer Change in gross employment_income Change in gov_balance
2026 −£32.0m +£31.7m −£21.2m
2027 −£32.1m +£31.9m −£21.2m

Here the correction raises wages by close to the employer NI saved, so part of it comes back as income tax and employee NI.

Tests

  • test_ni_threshold_freeze.py: the statute table now holds Fraction(5_000, 52). The strict xfail test_annual_secondary_threshold_is_statutory is now an ordinary test covering 2025 and 2026–2030, at ±£0.01. The edge cases now include both £4,992 and £5,000.
  • ni_class_1_employer.yaml: £5,251.20 / £8,251.20 / £45,251.20 become £5,250 / £8,250 / £45,250. New cases: £5,000 (2025-26) → 0; £4,996 (2026-27) → 0; £6,000 → £150; £40,000 in 2025-26 → £5,250.
  • ni_threshold_freeze.yaml: expectations now use £5,000.
  • Against main's parameter, 12 of the NI YAML cases fail. On this branch they pass.
  • Commands run: policyengine-core test policyengine_uk/tests/policy -c policyengine_uk (full YAML suite: 2,562 passed). pytest test_ni_threshold_freeze.py test_ni_class_1_age_properties.py (57 passed). pytest policyengine_uk/tests/ -m "not microsimulation" -n 6: 3,084 passed, 4 skipped, 2 xfailed (the five Employer NI annualises the secondary threshold as 52 × £96 = £4,992, not the statutory £5,000 #1967 strict xfails are now ordinary passing tests). Microsimulation-marked tests were not run because they need the full dataset.
  • ruff format / ruff check on the touched Python file: clean.

axiom: TheAxiomFoundation/rulespec-uk#438 queued

🤖 Generated with Claude Code

From 2025-26 store the secondary threshold as 5,000 / 52 a week, the
convention SI 2001/1004 reg 11(3A)(c) uses and the model already applies
to the primary threshold and upper earnings limit. 52 weeks now give the
reg 11(3A)(b) annual £5,000 instead of 52 x £96 = £4,992. The strict
xfail in test_ni_threshold_freeze.py becomes an ordinary test.

Fixes #1967

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@juaristi22 juaristi22 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.

Reviewed commit fe705bb41119436965fc7017b46371d7353e1e74. The £5,000 annual threshold correction is sound; no introduced formula defect found. All four annual threshold readers are consistent with the new internal weekly equivalent. Regulation 11(3A)(b) supports the annual amount; statutory weekly £96 and monthly £417 remain separate payroll-period amounts.

A1 — Please clarify the incidence setting behind the aggregate impact table (nonblocking).

The described comparison restores £96 through a reform with “nothing else changed”. With default employee incidence of 1, that reform also changes gross wages, so it does not isolate the parameter correction at fixed earnings.

Reproduced for a 2026 worker with £40,000 supplied through employment_income_before_lsr:

Run Calculated gross wages Employer NI
Corrected baseline £40,000.000 £5,250.000
Restore £96, default employee incidence £39,998.957 £5,251.043945
Restore £96, employee incidence explicitly 0 £40,000.000 £5,251.200195

Please state the actual setting used for the £32.0m/£32.1m table. If it used the default, label it a wage-adjusted reform comparison or rerun at fixed gross earnings. If incidence was already zero, adding that detail resolves this finding. I did not inspect the original aggregate script or independently rerun that table. Phrase the £1.20 as the employer-liability difference at fixed annual earnings above £5,000, rather than an employee paying employer NI.

For a population analysis, employee incidence of zero does not itself disable consumer/capital incidence; those settings should also be explicit if reporting household effects or a fully incidence-free comparison. This is the documentation issue raised in #2168.

Validation: 92 NI YAML cases passed; 15 independent household diagnostic rows completed, including five baseline liability assertions around the threshold. Local Hypothesis-dependent suites could not collect because that dependency is absent. GitHub Test, Lint and Python 3.11–3.14 install checks are successful at the reviewed commit.

The code correction is supported; the remaining finding concerns the description's impact methodology.

@vahid-ahmadi

Copy link
Copy Markdown
Collaborator Author

Thanks, María. You were right on A1.

The original setting. The −£32.0m / −£32.1m table compared this branch with a £96 reform under the default incidence settings, with no incidence parameter changed. So employee_incidence was 1 and the £96 run also lowered gross wages. It was a wage-adjusted comparison, not a fixed-earnings one. I reran that pair and it reproduces the −£32.0m and −£32.1m figures (−£31.98m and −£32.14m unrounded).

Rerun at fixed gross earnings. Same dataset (microcosm_uk_2024_25.h5, SHA-256 aa31bdf6…), branch code at fe705bb. Both runs set employee_incidence, consumer_incidence and capital_incidence to 0. Gross employment income is identical in the two runs (largest per-person difference £0).

Model year Employees liable (£5,000 threshold) Change in ni_class_1_employer Change in gov_balance
2026 30.62m −£36.7m −£36.7m
2027 30.77m −£36.9m −£36.9m

This is £1.20 times the number of employees liable, plus a negligible amount from people earning between £4,992 and £5,000. For reference, under the default settings gov_balance changes by −£21.2m in both years, because the higher wages bring back some income tax and employee NI.

Description updated. The impact section now states the incidence settings, shows the fixed-earnings table first and labels the old figures as a wage-adjusted comparison. The £1.20 now reads as the difference in employer liability at fixed annual earnings of £5,000 or more, not as something the employee pays.

🤖 Generated with Claude Code

@vahid-ahmadi
vahid-ahmadi marked this pull request as ready for review October 7, 2026 09:31

@juaristi22 juaristi22 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.

Reviewed fe705bb. The annual £5,000 secondary-threshold correction remains sound. A1 is resolved: the description now separates the fixed-earnings comparison (all three employer NI incidence parameters set to zero) from the wage-adjusted comparison under default incidence, and describes £1.20 as the employer-liability difference. CI is green at this head. I did not independently rerun the population table. Approving.

@vahid-ahmadi
vahid-ahmadi merged commit be4bf0d into main Oct 7, 2026
6 checks passed
@vahid-ahmadi
vahid-ahmadi deleted the fix-secondary-threshold-annual-5000 branch October 7, 2026 11:57
@MaxGhenis

Copy link
Copy Markdown
Collaborator

Enhanced FRS impact (post-merge check)

The impact above used the microcosm drill-2 file. This repeats it on the Enhanced FRS 2024-25 (private build, SHA-256 e433e532…, the file behind #1959's impact). It compares the merge commit be4bf0d9e with its first parent b7a73267. Both runs used one clean checkout, with the package installed editable from that checkout. Each package ran as its own baseline, with no reform and default incidence settings.

Model year Annual ST, before → after Employer NI gov_balance Liable employees paying less
2025 £4,992.00 → £5,000.00 −£35.9m −£35.9m 29.95m
2026 £4,992.00 → £5,000.00 −£36.1m −£36.1m 30.07m
2027 £4,992.00 → £5,000.00 −£36.3m −£36.3m 30.21m
2028 £4,992.00 → £5,000.00 −£36.4m −£36.4m 30.34m
2029 £4,992.00 → £5,000.00 −£36.9m −£36.9m 30.78m
2030 £4,992.00 → £5,000.00 −£37.2m −£37.2m 30.97m
2031 £5,091.84 → £5,100.00 −£38.1m −£38.1m 31.12m

2031 is the first CPI-uprated year after the freeze. It now uprates from £5,000 rather than £4,992.

These are close to the drill-2 figures above (−£36.7m in 2026, −£36.9m in 2027).

Checks run on every one of the 113,617 person records in every year:

  • Earnings fixed. The largest change in employment_income and in ni_class_1_income is £0. employer_ni_fixed_employer_cost_change and the capital and consumer incidence responses are £0 in both runs.
  • Differential. Each person's change in ni_class_1_employer equals 15% × (max(e − 52 × ST_after, 0) − max(e − 52 × ST_before, 0)) for people liable to secondary Class 1, and 0 for everyone else. There are no mismatches beyond float32 tolerance. No one's employer NI rises. Above £5,000 the cut is £1.20 a year, or £1.22 in 2031.
  • Nothing else moves. Income tax, employee NI and household_net_income are unchanged; the largest change in any household is £0. So the whole change shows up in gov_balance.

About the dataset: it predates the fix for PolicyEngine/policyengine-uk-data#504, which gave SPI-synthetic under-16s employment income. That does not affect this comparison. Secondary Class 1 needs an earner over 16 (ni_class_1_secondary_liable), so those records pay no employer NI in either run. In 2026, all 3.37m weighted people with Class 1 earnings of £5,000 or more who are not liable are under 16.

The scripts and outputs (aggregates only) are kept outside the repo. Follow-up: #2178 corrects the 2015-16 and 2016-17 secondary threshold (£155 → £156) and pins the annual threshold to reg 11(3A)(b) for every year from 2015-16.

MaxGhenis added a commit that referenced this pull request Oct 7, 2026
…ertion

docs/book/policy/model-baseline.md still said the model annualises the
secondary threshold as 52 x £96 = £4,992, which #2165 fixed.

At the annual threshold itself, the stored 5,000 / 52 annualises to
£4,999.999992, so employer NI there is exactly zero only after float32
rounding. Assert less than half a penny instead of exact zero (review
finding).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

Employer NI annualises the secondary threshold as 52 × £96 = £4,992, not the statutory £5,000

3 participants