Skip to content

Conversation

@vahid-ahmadi
Copy link
Collaborator

Copilot AI review requested due to automatic review settings November 21, 2025 16:01
@vahid-ahmadi vahid-ahmadi self-assigned this Nov 21, 2025
@vercel
Copy link

vercel bot commented Nov 21, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
policyengine-app Ready Ready Preview Comment Nov 27, 2025 7:05pm

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR adds a blog post analyzing the UK government's proposed £2,000 cap on salary sacrifice pension contributions. The analysis examines the policy's impact on households, government revenue, and income distribution using PolicyEngine's microsimulation model.

Key Changes

  • Adds comprehensive analysis of salary sacrifice cap proposal with detailed calculations and visualizations
  • Includes metadata entry for the new blog post with proper tags and author attribution
  • Updates changelog to reflect the minor version bump for new content

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/posts/posts.json Adds metadata entry for the salary sacrifice cap blog post with title, description, date, tags, filename, image, and author
src/posts/articles/uk-salary-sacrifice-cap.md New 216-line article with introduction, household impact analysis, budgetary analysis, distributional analysis, and conclusion sections with embedded Plotly visualizations
src/images/posts/salary-sacrifice-uk.jpg Binary image file for the blog post (370 lines of encoded data)
changelog_entry.yaml Updates version bump from patch to minor and changes description to reflect new blog post addition

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@MaxGhenis
Copy link
Contributor

Nice analysis! One question on the 13% employer haircut assumption:

The model assumes employers can identify exactly which employees use salary sacrifice and reduce their specific compensation by 13%. But if employers have that level of targeting ability, couldn't they also just restructure benefits to avoid the cap (e.g., increase employer pension contributions directly)?

There's a bit of an internal inconsistency:

  • If employers CAN target: They'd likely find workarounds rather than pass through costs
  • If employers CAN'T target: They'd spread any compensation adjustment across all employees, not just affected individuals

A more defensible assumption might be an employer cost-neutral scenario where:

  • Employers face higher NI costs on affected employees
  • They spread this across all employees via slightly lower raises/bonuses
  • Total employer compensation cost stays constant

This is more realistic because employers typically don't adjust individual compensation based on personal benefit elections (discrimination concerns, admin complexity). Compensation decisions are usually made at cohort/company level.

This would likely push the revenue estimate closer to the government's £2bn figure, with less concentrated impact on salary sacrifice users specifically.

Worth considering as a sensitivity analysis or revision to the main assumption?

@MaxGhenis
Copy link
Contributor

Follow-up with specific implementation suggestion:

Looking at the notebook, the current approach in create_ss_cap_reform() does:

# Current: targeted haircut on affected individuals
new_employment_income = emp_income + excess_ss_contrib * (1 - employer_response_haircut)

This applies the 13% haircut only to individuals with salary sacrifice > £2k, which assumes employers can target compensation reductions to specific employees based on their benefit elections.

Suggested alternative: Employer cost-neutral, broad-based response

A more defensible approach would spread the employer's increased NI cost across all employees:

def create_ss_cap_reform_v2(cap_amount: float = 2000):
    def modify(sim):
        for year in range(2026, 2031):
            ss_contrib = sim.calculate("pension_contributions_via_salary_sacrifice", period=year).values
            excess_ss_contrib = np.maximum(ss_contrib - cap_amount, 0)
            emp_income = sim.calculate("employment_income", period=year).values
            
            # Calculate total employer NI cost increase from excess becoming taxable
            # Employer NI rate is ~13.8% (or 15% from April 2025)
            employer_ni_rate = 0.138
            total_employer_ni_increase = (excess_ss_contrib * employer_ni_rate).sum()
            
            # Spread this cost across ALL employees (not just affected ones)
            total_employment_income = emp_income.sum()
            broad_haircut_rate = total_employer_ni_increase / total_employment_income
            
            # All employees get slightly reduced income
            new_employment_income = emp_income * (1 - broad_haircut_rate)
            
            # Affected employees also get excess converted to taxable income
            new_employment_income += excess_ss_contrib  # Full amount, no targeted haircut
            
            # Update employee pension contributions for affected individuals
            employee_pension = sim.calculate("employee_pension_contributions", period=year).values
            new_employee_pension = employee_pension + excess_ss_contrib
            
            sim.set_input("employment_income", year, new_employment_income)
            sim.set_input("employee_pension_contributions", year, new_employee_pension)
            sim.set_input("pension_contributions_via_salary_sacrifice", year, ss_contrib - excess_ss_contrib)
    
    return Scenario(simulation_modifier=modify)

Key differences:

  1. No targeted haircut - affected employees get 100% of excess converted (not 87%)
  2. Broad-based adjustment - ALL employees see a tiny reduction (~0.0X%) to offset employer's increased NI costs
  3. Total employer cost stays constant - more realistic labor market response

This should push revenue closer to the government's £2bn estimate and is more defensible from a labor economics perspective. Worth running as at least a sensitivity analysis?

@MaxGhenis
Copy link
Contributor

Revenue Calculation Issue

After investigating the £1.33bn revenue figure, I believe there's an error in the original calculation. Here's my analysis:

The Issue

The original analysis shows £1.33bn revenue, but when I trace through the tax mechanics, the correct figure should be approximately £0.53-0.60bn.

Why Income Tax Should Be Zero

I tested with a simple case (£50k earner with £5k salary sacrifice → £2k cap):

BASELINE:
- Employment income: £45,000 (after £5k SS)
- Income tax: £6,486

REFORMED (with £3k excess → £2.61k net after 13% haircut):
- Employment income: £47,610 (+£2,610)
- Employee pension contribution: £2,610 (redirected excess)
- Taxable income for IT: £45,000 (employment income minus pension = same as baseline)
- Income tax: £6,486 (UNCHANGED)
- Employee NI: £2,803 (+£209 vs baseline)

Key finding: Employee pension contributions reduce taxable income, so the income tax effect nets to zero. The only revenue is from NI.

Correct Revenue Breakdown

Using the FRS data (£3.60bn total salary sacrifice, £2.63bn excess above £2k cap):

Component Amount
Employee NI (8% on net excess) £0.18bn
Employer NI (15% on net excess) £0.34bn
Total £0.52bn

If using full excess (broad-based model): £0.60bn

Why Original Showed £1.33bn

The original used gov_balance difference between baseline and reformed simulations. I suspect the reformed simulation may not have properly applied the employee pension contribution offset to taxable income, causing income tax to be incorrectly counted.

£1.33bn / £0.53bn ≈ 2.5x, which matches roughly what we'd see if ~35% marginal income tax was incorrectly included.

Comparison to Government Estimate

  • Our correct estimate: £0.60bn
  • Government estimate: £2.0bn

The discrepancy is likely due to FRS underreporting salary sacrifice. External estimates suggest total SS is ~£20bn (vs our £3.6bn). Scaling up: £0.60bn × (20/3.6) ≈ £3.3bn.

Recommendation

  1. Verify that employee_pension_contributions properly reduces taxable income in the microsimulation
  2. Update revenue figures to reflect the corrected calculation
  3. Note the data limitations explaining the gap with government estimates

I've updated the blog post on the salary-sacrifice-uk branch with corrected figures using the broad-based employer response model.

@MaxGhenis
Copy link
Contributor

Data Calibration Note

The FRS-based salary sacrifice data significantly undercounts actual salary sacrifice activity:

Source Total Salary Sacrifice Implied from
FRS (PolicyEngine) £3.6bn Direct survey responses
External estimates ~£20bn HMRC/SPP £4.1bn NI cost (implying ~£27bn × 15% employer NI)

This explains the revenue gap:

  • PolicyEngine estimate: £0.6bn/year (proportionally correct for our £3.6bn data)
  • Treasury estimate: £2bn/year
  • Scaled PolicyEngine: £0.6bn × (20/3.6) ≈ £3.3bn/year (aligns with FT's £3-4bn estimate)

Recommendation: The policyengine-uk-data microdata should be calibrated to external salary sacrifice benchmarks, likely using HMRC's NI relief cost data as a calibration target. This would bring revenue estimates in line with Treasury/external scorekeeper figures.

See: HMRC Employer attitudes toward salary sacrifice

@MaxGhenis
Copy link
Contributor

Filed PolicyEngine/policyengine-uk-data#215 to track the salary sacrifice calibration work.

- Updates data from new FRS salary sacrifice imputation model
- Workers with SS contributions: 1.22M (was 588k)
- Total SS contributions: £6.93bn (was £3.60bn)
- Revenue estimate: £1.13bn (was £0.60bn)
- Updates distributional chart and example calculations

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Updated data section: 4.9m workers (15% of employees), £22.7bn total
- Updated revenue estimates: £3.2bn (PE) vs £4.9bn static / £4.7bn post-behavioural (OBR)
- Updated number above cap: 3.3m workers (68% of SS users)
- Policy takes effect April 2029
- Removed outdated £2bn FT estimate, replaced with official OBR figures

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Revenue by scenario chart showing £3.32bn to £7.58bn range
- Distributional impact chart with absolute/relative toggle
- Charts hosted on GitHub Pages from uk-salary-sacrifice-analysis repo

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- Update revenue estimate from £3.2bn to £3.3bn (matching £3.32bn in chart)
- Fix posts.json description: £1.1-2.3bn → £3.3-7.6bn
- Remove redundant Table 3 (data now shown in interactive chart)
- Correct OBR comparison: 35% → 32% below static estimate
- Add explanation of behavioural assumption differences

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
@MaxGhenis
Copy link
Contributor

Closing - moving to app-v2 repo

@MaxGhenis MaxGhenis closed this Nov 27, 2025
@github-project-automation github-project-automation bot moved this from Todo to Closed in policyengine-app Nov 27, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Closed

Development

Successfully merging this pull request may close these issues.

3 participants