Repository navigation
Conversation
Stage 1 of the salary sacrifice imputation ran the QRF for everyone not asked SALSAC and kept positive draws even without pay. The forest learnt the no-pay case from the 30 SALSAC respondents with a job but no recorded pay, three of whom report a sacrifice, so about 1.5k records (about 1.06m weighted, £11.3bn of the £25.8bn total in the 1.56.16 enhanced FRS) had a sacrifice and no pay. From 2029 policyengine-uk returns the excess over the £2,000 cap to employment income, so those people gained pay. - limit_salary_sacrifice_to_pay: every amount, reported or imputed, is between zero and the person's pay. No FRS 2024-25 respondent who reports a sacrifice and has pay reports more than that pay. - The QRF trains only on SALSAC respondents with pay. - Stage 2 moves employee pension to salary sacrifice up to the person's pay; any remainder stays as employee pension. - Tests: Hypothesis properties of the limit and of the whole imputation (stub model), and built-dataset checks that nobody has a sacrifice without pay or above it, and nobody without pay has employment income in 2029. Both built-dataset checks fail on the 1.56.16 file. - hypothesis added to the dev extras. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
policyengine-uk is gaining the same limit (pension_contributions_via_salary_sacrifice_from_pay). Where the installed model has it, the built dataset's salary sacrifice must already equal it. Skipped on a policyengine-uk without the variable. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
21 of 52 tasks
Stage 2 converts a pension contributor's employee contribution into salary sacrifice but left their pay unchanged. FRS pay is before that deduction, and policyengine-uk treats employment income as pay after any sacrifice, so a converted donor kept the same pay, lost the relief on the contribution and paid more income tax. Removing the phantom no-pay users enlarges stage 2's shortfall: at pre-calibration weights the extra donors (1.32m weighted) moved £2.95bn and paid £0.90bn more income tax for that reason alone. Stage 2 now takes the amount moved out of pay, so taxable pay is unchanged, and moves at most half the pay, so the sacrifice never exceeds the pay that remains. Tests: pay falls by exactly the amount moved, pay plus salary sacrifice and the pension total are unchanged for every donor, nobody else's pay changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review (subfleet 20261002-093645-ukdata-518-ss-review) found the limits used employment_income from a Microsimulation of the 2024 survey-year dataset, which calculates 2025 by default and uprates pay by 5.3% but not salary sacrifice. Sacrifices could therefore reach 105.3% of stored pay, and stage 2 could move up to 52.65% of stored pay and leave less than that. CI on a99a1ef caught it: 4 people in the TESTING build sacrificed more than their pay. - The limits and stage-2 donor pay use the stored employment_income column. QRF predictors still come from the simulation, as in training. - The model cache is now salary_sacrifice_paid_respondents.pkl, so a forest cached from the old training set is not reused. - The no-training-data error names the actual condition; docstrings say the upper bound is an imputation bound, not a legal one, and that only predictions for people with pay are kept. - Wiring tests run at 2024 and 2025; the 2024 cases fail on a99a1ef. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review r2 (subfleet 20261003-090151-ukdata-518-ss-review-r2, APPROVE) nits: the stage 2 comments still said donors keep their full employee pension and become below-cap users, and "keeps taxable pay unchanged" claimed more than the code guarantees (pay plus sacrifice is unchanged). The stored pay array is copied before use. No change in behaviour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 3, 2026
…s-pay # Conflicts: # pyproject.toml # uv.lock
MaxGhenis
marked this pull request as ready for review
October 11, 2026 15:30
Contributor
Author
|
Hand-off to the UK hub (session local_bfbe2fd0 is closing).
|
4 tasks
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The salary sacrifice imputation gave people without pay a salary sacrifice, and converted pension contributors to salary sacrifice without taking the sacrifice out of their pay. This PR limits every amount to the person's pay and takes stage 2's sacrifice out of the donor's pay.
In a build of current
main(2b9c1c2), 1,891 records (0.96m people, weighted, 2025) hold £7.7bn of the £28.1bn salary sacrifice total while having no pay. With this PR there are none.The defects
Stage 1 drew a sacrifice for people without pay.
Stage 2 left the donor's pay unchanged.
The first version of the limit read uprated pay. A
Microsimulationof the 2024 dataset calculates 2025 by default and uprates pay by 5.3% but not salary sacrifice. Review r1 caught it.The fix
limit_salary_sacrifice_to_pay: every amount, reported or imputed, is between zero and the person's stored pay. The upper bound is an imputation bound, not a legal one: of the 300 FRS 2024-25 SALSAC respondents who report a sacrifice, none with pay reports more than that pay (out/ss_facts4_base_frs.txt).Invariants
Tests
Evidence
Two production-settings builds (512 epochs) on one machine:
mainat 2b9c1c2 and this branch at 99916cb (this PR merged with thatmain). Both retrained every imputation model, and used the same frozen HTTP target downloads and a seeded torch generator. I did not build the same commit twice, so differences between the two builds include whatever the calibration path does when its inputs change. Model figures use policyengine-ukmainat 4857dc75f (which includes #2063), one realMicrosimulationper dataset.Before calibration the two datasets differ in three person columns only: salary sacrifice, employee pension contributions and employment income.
Holding those weights fixed, modelled 2025 income tax by stage-2 cohort (aggregates in
stage2_cohorts4.json):mainalready convertsThe second row shows the conversion is close to income-tax-neutral once pay falls with it (those donors' National Insurance falls, as it does under a sacrifice). The first row is the same correction applied to donors whose pay
mainleft unchanged.After calibration (saved file, 2025):
Calibration fit, 645 national targets at the last logged epoch of the constituency solve (whose weights the saved file carries):
out/uprating_ratio_fix4.txt). On the saved file at 2025, contributions are £28.1bn onmain(+13.6% against the target) and £25.4bn with this PR (+2.7%).Weights:
Household weights in the two builds correlate at 0.965; households holding 44% of
main's weight move by more than 10%.Headline aggregates (2025):
Salary sacrifice cap (government balance, a current-law simulation against one with the cap set to infinity, same dataset, 2029): +£1.22bn on the
mainbuild, +£1.93bn with this PR. The sacrifice above the cap held by people with pay rises from £10.5bn to £13.7bn.Earlier builds against the 2 October
main(b45c373), before the HMRC relief targets returned in #529, gave the same direction: contributions +9.4% → −2.8%, overall fit broadly similar (within 5% / 10%: 63.7% / 84.0% → 64.2% / 84.6%).For the release
Review
Merging this PR is a data release, so it waits for the batched release.
axiom: n/a: data
🤖 Generated with Claude Code