Repository navigation
Document employer National Insurance incidence assumptions - #2469
Conversation
|
Formatted the page’s Python fence in c9375f6 to resolve the Lint failure. The example’s AST, surrounding prose, other five authored files and 1,769 protected model/test files are unchanged. All five local lint/guard checks pass with pinned Ruff 0.17.0, and independent verification is CLEAN. New-head hosted CI is pending. |
vahid-ahmadi
left a comment
There was a problem hiding this comment.
Fine once the parameter descriptions are shortened.
@juaristi22, the page is accurate. I checked each claim about the mechanism against the code and reran the worked example. The points below are about where the text lives and two small source details.
- should:
policyengine_uk/parameters/gov/contrib/policyengine/employer_ni/employee_incidence.yaml:2,consumer_incidence.yaml:2,capital_incidence.yaml:2. Eachdescriptionis now a paragraph of 70 to 90 words. It mentions holders,Simulationconstruction and dataset attributes. This field is the text users see for the parameter in the app, and the siblingexempt_employer_pension_contributions.yamlkeeps one sentence. Please cut each to one sentence, for example "Share of the fixed-employer-cost pay change passed to employees in the same year; set all three incidence parameters to 0 to disable these channels." The README and the new page already carry the detail. - nit:
docs/book/assumptions/employer-ni-incidence.md:186cites OBR paragraph 2.14 for the roughly 60% pass-through, but #2168 quotes the same wording from Box 2.1. The OBR site blocked my download, so I could not check which is right. Please confirm the location. Secondary sources also describe the OBR's 60% as going to "workers and consumers, via lower wages and higher prices", not to employees alone. A check of the exact wording would be worth it. - nit: the page and README use US spellings ("modeled", "modeling", "labor") at
employer-ni-incidence.md:20,43,57,71andREADME.md:5,17. Most ofdocs/bookuses British spelling. - nit: the issue also asked the page to say when the default is the better headline. The PR body says no preferred default is claimed, which is a fair choice. If that is the intent, a short sentence on the page would close that point of the issue.
- nit: the parameter
referenceentries point at this repo's own code. That is honest, as there is no external source, but it might read better as a note in the description or README than as a legal reference.
What I checked
- Read the full diff and #2168.
- Read
employer_ni_fixed_employer_cost_change.py, both residual-incidence formulas,household_tax.py,household_net_income.pyandsimulation.py:350. Each statement about guards, the below-threshold rule,household_taxinclusion, exclusion fromgov_taxand the constructor remapping matches the code. - Ran the page's code block from a fresh venv at head
c9375f6a. All four rows reproduce to the penny: +301.72 / −60.34 / −24.14 / −301.72 / +217.24 / −217.24, then +350.00 with zeros for the other three rows. Both residual channels return 0 in every row. - Hand-checked the first row: £45,250 baseline cost, (45,250 + 0.16 × 5,000) / 1.16 = £39,698.28, so −£301.72 in pay. 20% and 8% of that give the tax and NI rows.
- The secondary-threshold parameter is stored as 5,000 / 52, so the £5,000 in the text is right.
policyengine-core test policyengine_uk/tests/policy/baseline/contrib/policyengine/employer_ni -c policyengine_uk: 10 passed.- The new page is picked up by the
docs/book/assumptions/*.mdpattern inmyst.yml. The README's relative link resolves. - Hosted CI on head
c9375f6a: all checks pass, including Test / Python 1-4, YAML, Lint and both docs builds. The fragment typechangedfits a docs-only change, and the body has anaxiom: n/aline. - No clash with other open PRs that I found. #2386 adds other pages under
docs/book/assumptions/but no shared files. - Could not check: the OBR EFO text itself (blocked download), and any population run.
🤖 Generated with Claude Code
c9375f6 to
f72a29b
Compare
|
Addressed item 1 in f72a29b: all three parameter descriptions are now single sentences and explain how to disable the incidence channels. Values, dates, defaults and references are unchanged. After rebasing onto main at 7ad002f, the parameter-description test and all 10 employer-NI YAML cases passed, along with Ruff and the PR evidence check. |
Aggregate impact on certified data: passBase Checks: 0 broken by this change, 1 failing on both sides, 0 fixed. Moves flagged for review: 0 (thresholds in Notes
Moves flagged for reviewNone. Spending by benefit and service
Caseloads
Household net income by decileNo household's net income changes in any year. No change in any year: receipts by tax head; government balance, each component counted once; model totals; components the model counts twice; household income totals; population; poverty rates; people in poverty; inequality; mean household net income in each side's own deciles; standard reform costings (change in gov_balance); standard reforms: change in each reform's target. Checks failing on both base and head (1)
Counted-once checkEach of 84 components was raised by £1 per unit in 2026-27, and every head of its totals was read household by household: its own head must rise by exactly £1, no other tax head may move, no other benefit may rise, and the model's own total must move by what its heads moved. A head that breaks a rule is measured again twice: with every other head held at its baseline value, and with every other head held at its value after the first £1 while the component rises again. It fails where either still shows the response. 0 failed; 0 were skipped.
Provenance
Dataset: Receipts by tax head: head levels
Spending by benefit and service: head levels
Caseloads: head levels
Government balance, each component counted once: head levels
Model totals: head levels
Components the model counts twice: head levels
Household income totals: head levels
Population: head levels
Poverty rates: head levels
People in poverty: head levels
Inequality: head levels
Mean household net income in each side's own deciles: head levels
Standard reform costings (change in gov_balance): head levels
Standard reforms: change in each reform's target: head levels
|
vahid-ahmadi
left a comment
There was a problem hiding this comment.
@juaristi22 round 2, at f72a29be5. The branch was rebased, so I compared this PR's own diff before and after. The only change is the three parameter descriptions.
Verdict: the round-1 should is fixed. Approving.
Round-1 points
- Parameter descriptions: fixed.
employee_incidence.yaml,consumer_incidence.yamlandcapital_incidence.yamlnow have one sentence each, ending with how to switch the channels off. Values, dates, labels and references are unchanged. The detail is still on the new page and in the README. - OBR paragraph 2.14 or Box 2.1: still open (nit). The page still cites "paragraph 2.14" (
employer-ni-incidence.md:186). Please check this against the EFO before merge if you can. - US spellings: still open (nit). "modeled", "labor" and similar are still at
employer-ni-incidence.md:20,43,57,71. - and 5. Nits on the preferred default and the self-referencing
referenceentries: unchanged. Both were optional.
No new findings.
What I checked
- Compared the PR's diff at
c9375f6a5with its diff atf72a29be5. Only the threedescriptionfields differ. - Fresh worktree and uv venv at the head.
policyengine-core testontests/policy/baseline/contrib/policyengine/employer_ni: 10 passed.test_parameter_descriptions.py: passed. - Merges cleanly with current main (
8b12bce94) and with #2490. CI at the head: all 16 checks pass, and the aggregate-impact report shows no change. - Not checked: the OBR EFO text itself.
🤖 Generated with Claude Code
Fixes #2168.
Employer National Insurance reforms can change wages and other household incidence
channels, so their gross NI yield and net government balance answer different questions.
The existing parameter descriptions do not explain those assumptions or how to control
them.
This documentation expands the three incidence parameters and their README, adds an
Assumptions page, and refreshes the worked household comparison after the secondary
threshold correction in #2165. It explains the earnings-input route, the residual
consumer and capital channels, the three-zero control, and how to match an official
costing's definition. Parameter values, dates and formulas are unchanged. No empirical
justification or preferred incidence default is claimed.
axiom: n/a: Documentation and descriptive metadata only; no policy values, eligibility rules or calculated model behaviour change.
Validation:
make formatpassed with 1,495 Python files unchanged. Parsed parameter values, dates, units and labels are unchanged, as are 1,769 protected model/test files.Local validation used Python 3.13.14, core 3.32.27 and Hypothesis 6.168.2; the lockfile specifies core 3.33.2 and Hypothesis 6.169.0. Hosted CI will check the locked environment. No representative population estimate or empirical default calibration was run.