Document that employer NI reforms pass 100% to wages by default, so gov_balance is not a static costing #2168
Description
Revised after review (6 Oct 2026): following juaristi22's review, which reproduced both table rows under PE-UK 2.122.2 / core 3.32.17:
- "Any reform changes wages" is now limited to cases where
employment_incomeis computed fromemployment_income_before_lsr. A directly suppliedemployment_incomeoverrides the response. - New documentation requirement: setting
employee_incidenceto 0 does not switch off all incidence in a population simulation. Consumer and capital incidence (0.5 each) then become active. The household table cannot show this. To disable every response, set all three parameters to 0. - The docs should report gross employer-NI yield, net government balance and household incidence as separate outputs. They should also match the definition of the official costing being compared, rather than assuming every official headline is static.
- Worked examples should be refreshed after Annualise the employer NI secondary threshold to the statutory £5,000 #2165 lands.
Summary
gov.contrib.policyengine.employer_ni.employee_incidence defaults to 1 from 2010. Its whole description is "Fraction of employer NI that is borne by employees." It has no reference and no rationale, and there is no docs page. The parameter folder's README.md contains only the heading "# Employer NI".
With the default, a reform to the employer NI rate, secondary threshold or pension exemption also changes employment_income, through employer_ni_fixed_employer_cost_change, when employment_income is computed from its components. employment_income is the sum of employment_income_before_lsr, employer_ni_fixed_employer_cost_change and other terms (variables/input/employment_income.py). If a dataset or situation supplies employment_income directly, that value overrides the formula and wages do not respond. When the formula is used, employer cost is held fixed and the pay change is passed to employees in full. Lower pay then lowers income tax and employee NI. So gov_balance for an employer NI reform is a net figure that already includes that wage pass-through. It is not a static gross-liability costing. Before comparing it with an official figure, check how that official costing is defined.
consumer_incidence and capital_incidence (both 0.5, also with no references) apply to the change in employer cost not passed to employees. With the default employee_incidence = 1 they return zero (employer_ni_response_*_incidence.py). With employee_incidence = 0 they become active in a population simulation, and each takes half of the employer-cost change. Both formulas also return zero in a single-household simulation without a dataset, so the household table below does not show their population effects.
Reproduction
from policyengine_core.reforms import Reform
from policyengine_uk import Simulation
situation = {
"people": {"a": {"age": {2026: 40}, "employment_income_before_lsr": {2026: 40_000}}},
"benunits": {"b": {"members": ["a"]}},
"households": {"h": {"members": ["a"], "region": {2026: "LONDON"}}},
}
VARS = ["ni_employer", "income_tax", "ni_employee", "employment_income", "gov_balance"]
base = {v: float(Simulation(situation=situation).calculate(v, 2026).sum()) for v in VARS}
for incidence in [1, 0]:
reform = Reform.from_dict({
"gov.hmrc.national_insurance.class_1.rates.employer": {"2026-01-01.2100-12-31": 0.16},
"gov.contrib.policyengine.employer_ni.employee_incidence": {"2010-01-01.2100-12-31": incidence},
}, country_id="uk")
sim = Simulation(situation=situation, reform=reform)
print(incidence, {v: round(float(sim.calculate(v, 2026).sum()) - base[v], 2) for v in VARS})The example supplies employment_income_before_lsr, so employment_income can respond. On 2.122.2 (reproduced independently by juaristi22 at 4ec7ef1d, core 3.32.17), raising the employer rate from 15% to 16% for one £40,000 employee in 2026-27 gives:
Δ ni_employer |
Δ income_tax |
Δ ni_employee |
Δ employment_income |
Δ gov_balance |
|
|---|---|---|---|---|---|
employee_incidence = 1 (default) |
+£301.79 | −£60.36 | −£24.14 | −£301.79 | +£217.29 |
employee_incidence = 0 |
+£350.08 | 0 | 0 | 0 | +£350.08 |
The default reports 38% less revenue than the fixed-wage figure for this person. These numbers use the secondary threshold before the correction in #2165, so they should be refreshed once that lands.
For comparison, the OBR's October 2024 EFO reports the employer NICs rise as raising £25.7bn by 2029-30 "before allowing for its indirect effects on the economy" (para 1.7). Its forecast assumes firms "are only able to pass on around 60 per cent of the cost to employees in the short term" (Box 2.1) (OBR, Economic and fiscal outlook, October 2024). PolicyEngine's default is a full, immediate pass-through, at the far end of that range. Other official headlines may be defined differently, so each comparison needs its own definition check.
What's missing
This is a documentation gap, not a code bug. The default is a reasonable long-run modelling choice, but users scoring a Budget line can't tell from the parameter metadata that it is the default, when it applies, or how it moves gov_balance. This issue does not propose an empirical estimate for the preferred default.
Suggested fix
-
Parameter metadata and README. Expand the description and metadata of
employee_incidence.yaml(and the README) to say:- 1 means employer cost is held fixed and the whole change passes into gross pay in the same year, and
gov_balancethen nets off the resulting income tax and employee NI changes; - the wage response only operates when
employment_incomeis computed fromemployment_income_before_lsr; a directly suppliedemployment_incomeoverrides it; - add a reference for the choice, or say that none is claimed.
Do the same for
consumer_incidenceandcapital_incidence. Note that they apply to the employer-cost change not passed to employees, that they are inactive at the default, and that they become active (0.5 each) whenemployee_incidenceis lowered in a population simulation. - 1 means employer cost is held fixed and the whole change passes into gross pay in the same year, and
-
Docs section with the table above, covering:
- Three separate outputs: gross employer-NI yield (
ni_employer), net government balance (gov_balance), and household incidence. Say which one to read for which purpose. - Fixed-wage gross-liability comparison: set
employee_incidenceto 0 and readni_employer. This is valid for that comparison. - Population analysis with no incidence responses: set
employee_incidence,consumer_incidenceandcapital_incidenceall to 0. Setting onlyemployee_incidenceto 0 moves the incidence into prices and capital rather than switching it off. - Matching official costings: compare against the definition used by the official costing in question (for example, the OBR's pre-behavioural figure), rather than assuming every Treasury or OBR headline excludes indirect effects. Also say when the default is the better headline.
- Refresh the worked numbers after Annualise the employer NI secondary threshold to the statutory £5,000 #2165 lands. Restoring the threshold through a reform also triggers the wage response unless it is disabled.
- Three separate outputs: gross employer-NI yield (
-
Tests (optional). Add a YAML test that pins both rows of the table, so a change to the default or the mechanism is visible. If feasible, add a small dataset-backed test showing consumer and capital incidence turning on when
employee_incidence = 0.
🤖 Generated with Claude Code
Activity
juaristi22 commentedon Oct 6, 2026
The undocumented default and both household rows reproduce under PE-UK 2.122.2 (
4ec7ef1d30a2f476094a7fba6abdfe494856f3f9), core 3.32.17. For the issue's £40,000 worker and employer-rate increase from 15% to 16%,gov_balancerises £217.29 with full employee incidence and £350.08 with employee incidence zero.Two qualifications should be included in the documentation:
employment_income_before_lsr, allowingemployment_incometo respond. Supplyingemployment_incomedirectly overrides that formula, so “any reform changes wages” is too broad. Employment-income formula.Setting employee incidence to zero and reading
ni_employeris appropriate for the stated fixed-wage gross-liability comparison. A population analysis intended to disable these incidence responses should explicitly set all three parameters to zero. Document gross employer-NI yield, net government balance and household incidence as separate outputs, and match the definition of the official costing being compared rather than assuming every Treasury headline excludes indirect effects.This also affects #2165's aggregate comparison: restoring £96 through a reform invokes the wage response unless disabled. The numbers reproduced here use the pre-#2165 threshold; refresh worked numerical examples after that correction lands. The documentation gap is confirmed; this check does not establish an empirical estimate for the preferred incidence default.