Skip to content

Document that employer NI reforms pass 100% to wages by default, so gov_balance is not a static costing #2168

Open

Description

@vahid-ahmadi

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_income is computed from employment_income_before_lsr. A directly supplied employment_income overrides the response.
  • New documentation requirement: setting employee_incidence to 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

  1. 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_balance then nets off the resulting income tax and employee NI changes;
    • the wage response only operates when employment_income is computed from employment_income_before_lsr; a directly supplied employment_income overrides it;
    • add a reference for the choice, or say that none is claimed.

    Do the same for consumer_incidence and capital_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) when employee_incidence is lowered in a population simulation.

  2. 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_incidence to 0 and read ni_employer. This is valid for that comparison.
    • Population analysis with no incidence responses: set employee_incidence, consumer_incidence and capital_incidence all to 0. Setting only employee_incidence to 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.
  3. 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

  1. juaristi22 commented on Oct 6, 2026

    @juaristi22
    Collaborator

    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_balance rises £217.29 with full employee incidence and £350.08 with employee incidence zero.

    Two qualifications should be included in the documentation:

    1. The earnings input matters. The example supplies employment_income_before_lsr, allowing employment_income to respond. Supplying employment_income directly overrides that formula, so “any reform changes wages” is too broad. Employment-income formula.
    2. Zero employee incidence does not disable all incidence in a population simulation. Consumer and capital incidence remain 0.5 each and become active on the employer-cost change. Both formulas return zero for single-household simulations without a dataset, so the household table does not demonstrate their population effects. Consumer response, capital response.

    Setting employee incidence to zero and reading ni_employer is 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.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions